const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); const sharp = require('sharp'); require('../src/common'); const library = require('../src/web/lib/media/weather-icon-library'); const weatherIcons = require('../src/web/public/js/shared/weather-icons'); const vm = require('node:vm'); const placeholderContext = { window: {} }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../src/web/public/js/shared/placeholder-utils.js'), 'utf8'), placeholderContext); const placeholders = placeholderContext.window.placeholderUtils; test('weather icon picker warns about each missing condition', function () { const source = fs.readFileSync(path.join(__dirname, '../src/web/public/js/settings/weather-icons.js'), 'utf8'); assert.match(source, /window\.showToast\('Add images for: ' \+ labels\.join\(', '\) \+ '\.', 'warning'\)/); assert.match(source, /replace\(\/\^Upload\\s\+\|\\s\+icon\$\/g, ''\)/); }); test('weather editor and player render day or night variants from one selected set', function () { const sets = [{ id: 'abc', name: 'Colour', icons: { clear: '/media/weather-icons/abc/clear-123abc.png' }, nightIcons: { clear: '/media/weather-icons/abc/night-clear-456def.png' } }]; const location = { id: 1, responseJson: { current: { weather_code: 0, is_day: 1 } } }; let editor; const editorContext = { window: { pulseWeatherIconSets: sets, pulseWeatherIcons: weatherIcons, placeholderUtils: placeholders, pulseRegionTypes: { register: function (_type, module) { editor = module; } } }, document: { addEventListener: function () {} } }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../src/web/public/js/regions/type/weather.js'), 'utf8'), editorContext); const region = { id: 1, region_key: 'weather', label: 'Forecast', baseStyle: '' }; const content = { weather_location_id: 1, weather_icon_set_id: 'abc', value: '{{current.icon(48,24)}}' }; const card = editor.renderEditorCard({ region: region, current: content, weatherLocations: [location] }); assert.match(card, /name="region_weather_icon_set_id_1"/); assert.match(card, /value="abc" selected/); assert.match(card, /row g-3 align-items-end">
Assets/);
assert.match(html, /nav-icon bi bi-folder2-open/);
assert.ok(html.indexOf('User management') < html.indexOf('Assets'));
assert.match(html, /action="\/settings\/weather-icons"/);
assert.match(html, /name="icon_clear"/);
assert.match(html, /class="page-header"/);
assert.match(html, /Upload and manage shared icon sets for weather regions/);
assert.match(html, /class="card card-outline card-primary mb-4 collapsed-card"/);
assert.match(html, /class="card-title mb-0">Upload icon set/);
assert.doesNotMatch(html, /Add a PNG or WebP image for every weather condition/);
assert.match(html, /data-lte-toggle="card-collapse"/);
assert.match(html, /data-lte-icon="expand"/);
assert.match(html, /data-lte-icon="collapse"/);
assert.match(html, /action="\/settings\/weather-icons" enctype="multipart\/form-data" class="card-body p-0"/);
assert.match(html, /class="card-title">Managed icon sets/);
assert.match(html, /data-table-sort-key="status">Status/);
assert.equal((html.match(/class="app-content"/g) || []).length, 1);
assert.doesNotMatch(html, //g) || []).length, 5);
assert.match(response.html, /Set 25/);
assert.doesNotMatch(response.html, /Set 00/);
assert.match(response.html, /data-bs-toggle="modal"/);
assert.match(response.html, /href="\/assets\/css\/weather-icons.css"/);
await handlers['/settings/weather-icons'][1]({ query: { search: 'Set 29', page: '2' }, currentUser: currentUser }, response, fail);
assert.equal((response.html.match(/ /g) || []).length, 1);
assert.match(response.html, /Set 29/);
await handlers['/settings/weather-icons'][1]({ query: { search: 'Set', page: '2' }, currentUser: currentUser }, response, fail);
assert.match(response.html, /search(?:=|=)Set/);
} finally {
library.readWeatherIconSets = originalRead;
}
});
test('weather sets require every condition image and support replacement', async function () {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'weather-icons-'));
try {
const buffer = await sharp({ create: { width: 16, height: 16, channels: 4, background: '#ff0000' } }).png().toBuffer();
const allFiles = library.CONDITIONS.map(function (condition) { return { fieldname: 'icon_' + condition.key, buffer: buffer }; });
const svgBuffer = Buffer.from('');
await assert.rejects(library.saveWeatherIconSet(root, 'Partial', allFiles.slice(0, 2)), /every weather condition.*Overcast/);
const first = await library.saveWeatherIconSet(root, 'Colour', allFiles);
const second = await library.saveWeatherIconSet(root, 'Outline', allFiles);
assert.notEqual(first.id, second.id);
assert.equal(Object.keys(first.icons).length, library.CONDITIONS.length);
assert.match(first.icons.clear, /^\/media\/assets\/weather-icons\//);
const disabled = library.setWeatherIconSetEnabled(root, first.id, false);
assert.equal(disabled.enabled, false);
assert.equal(library.readWeatherIconSets(root).find(function (set) { return set.id === first.id; }).enabled, false);
assert.ok(library.collectWeatherIconSyncOperations(root).some(function (operation) {
return operation.type === 'put' && operation.uploadPath === first.icons.clear;
}));
assert.equal(fs.existsSync(path.join(root, 'assets', 'weather-icons', first.id, path.basename(first.icons.clear))), true);
library.setWeatherIconSetEnabled(root, first.id, true);
assert.equal(library.getWeatherManifestPath(root), path.join(root, 'assets', 'weather.json'));
const svgSet = await library.saveWeatherIconSet(root, 'Vector', library.CONDITIONS.map(function (condition) { return { fieldname: 'icon_' + condition.key, buffer: svgBuffer }; }));
const svgFileName = path.basename(svgSet.icons.clear);
assert.match(svgFileName, /\.svg$/);
assert.match(fs.readFileSync(path.join(root, 'assets', 'weather-icons', svgSet.id, svgFileName), 'utf8'), /