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">
Colour<\/option>/); assert.doesNotMatch(disabledCard, /value="abc" selected disabled/); sets[0].enabled = true; const preview = editor.renderPreview(region, content, { weatherLocations: [location] }); assert.match(preview, /clear-123abc.png/); assert.match(preview, /width:48px;height:24px/); const topPreview = editor.renderPreview(region, Object.assign({}, content, { value: '{{current.icon(48,96,top)}}' }), { weatherLocations: [location] }); assert.match(topPreview, /vertical-align:top/); let player; const playerContext = { window: { pulseWeatherIcons: weatherIcons, placeholderUtils: placeholders, pulsePlayerRegionTypes: { register: function (_type, module) { player = module; } } }, initialData: { weatherLocations: [location], weatherIconSets: sets }, escapeHtml: String, renderEditorJsContent: String }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../src/player/regions/weather.js'), 'utf8'), playerContext); assert.match(player.renderRegion(region, content), /clear-123abc.png/); assert.match(player.renderRegion(region, Object.assign({}, content, { value: '{{current.icon(48,96,bottom)}}' })), /vertical-align:bottom/); location.responseJson.current.is_day = 0; assert.match(player.renderRegion(region, content), /night-clear-456def.png/); location.responseJson.current.is_day = 1; assert.match(player.renderRegion(region, Object.assign({}, content, { weather_icon_set_id: '' })), /bi-sun/); location.responseJson.current.weather_code = 61; assert.match(player.renderRegion(region, content), /bi-cloud-rain/); }); test('weather icons map exact WMO codes and fall back for missing or unsafe images', function () { assert.equal(weatherIcons.conditionForCode(3), 'overcast'); assert.equal(weatherIcons.conditionForCode(85), 'snow'); assert.equal(weatherIcons.conditionForCode(null), ''); assert.equal(weatherIcons.conditionForCode(4), ''); const sets = [{ id: 'abc', icons: { clear: '/media/weather-icons/abc/clear-123abc.png', rain: 'javascript:alert(1)' } }]; assert.equal(weatherIcons.imageForCode(sets, 'abc', 0), sets[0].icons.clear); assert.equal(weatherIcons.imageForCode(sets, 'abc', 61), ''); assert.equal(weatherIcons.imageForCode(sets, 'abc', 45), ''); sets[0].enabled = false; assert.equal(weatherIcons.imageForCode(sets, 'abc', 0), sets[0].icons.clear); assert.match(weatherIcons.renderImage(sets[0].icons.clear, 48, 24), /width:48px;height:24px/); assert.match(weatherIcons.renderImage(sets[0].icons.clear, 48, 24), /display:inline-flex;vertical-align:middle;align-items:center;justify-content:center/); assert.match(weatherIcons.renderImage(sets[0].icons.clear, 48, 24), /vertical-align:middle.*line-height:0/); assert.match(weatherIcons.renderImage(sets[0].icons.clear, 48, 96, 'bottom'), /vertical-align:bottom/); assert.match(weatherIcons.renderImage(sets[0].icons.clear, 48, 24), /object-fit:contain/); }); test('weather icon edit controls use the standard colour and save before cancel', function () { const html = require('../src/web/view').renderView('settings/weather-icons/list', { sets: [{ id: 'example', name: 'Example', enabled: true, conditions: [] }], currentUser: { permissionKeys: ['weather-icons.read', 'weather-icons.update'] } }); assert.match(html, /class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#edit-set-example">Edit<\/button>/); assert.match(html, /