diff --git a/ranges/README.md b/ranges/README.md index da36cb6..fca1a5a 100644 --- a/ranges/README.md +++ b/ranges/README.md @@ -27,7 +27,7 @@ const data = await ranges('alpha', { now: '2026-12-15' }) ## Structure -This module will return a medium-sized object with the following structure, with each property being in the context of the range it's under: +This module will return a medium-sized object with the following structure, with each property being in the context of the range it's under. A field with nothing to point to, like `oldestSecurity` on a release line that hasn't had a security release yet, is `undefined` (and left out of JSON). - all: - versions (Array): An array of versions. Leaves out Alpha releases. diff --git a/ranges/index.js b/ranges/index.js index 8a4de9e..5062fc6 100644 --- a/ranges/index.js +++ b/ranges/index.js @@ -150,12 +150,12 @@ async function generateRanges(filter, options) { if (data[line].support?.phases.current === 'start') { ranges.current.versions.push(key); // add current iteration to the versions array if the phase is `start` which means it'll be current. - if ( - ranges.current.oldestSecurity === undefined && - ranges.current.newestSecurity === undefined - ) { - ranges.current.oldestSecurity = `${data[line].security.oldest}`; - ranges.current.newestSecurity = `${data[line].security.newest}`; + // only the first Current line fills these in. checking the versions + // count rather than the fields, since a line without a security + // release leaves them undefined. + if (ranges.current.versions.length === 1) { + ranges.current.oldestSecurity = data[line].security.oldest; + ranges.current.newestSecurity = data[line].security.newest; } } } @@ -176,14 +176,12 @@ async function generateRanges(filter, options) { // TODO: do we want to include all versions in the release line, even prior to it being minted LTS? ranges['lts/latest'].versions.push(key); - if (ranges['lts/latest'].oldestSecurity === undefined) { - // only checking if this one is undefined, since everything else _should_ be undefined if this one is. saves some processing power. - ranges['lts/latest'].oldestSecurity = - `${data[line].security.oldest}`; - ranges['lts/latest'].newestSecurity = - `${data[line].security.newest}`; - ranges['lts/latest'].oldestLts = `${data[line].support.lts.oldest}`; - ranges['lts/latest'].newestLts = `${data[line].support.lts.newest}`; + if (ranges['lts/latest'].versions.length === 1) { + // only the first LTS line fills these in, same as 'current' + ranges['lts/latest'].oldestSecurity = data[line].security.oldest; + ranges['lts/latest'].newestSecurity = data[line].security.newest; + ranges['lts/latest'].oldestLts = data[line].support.lts.oldest; + ranges['lts/latest'].newestLts = data[line].support.lts.newest; } } } @@ -203,26 +201,21 @@ async function generateRanges(filter, options) { ) { ranges['lts/maintenance'].versions.push(key); - if ( - ranges['lts/maintenance'].newestSecurity === undefined && - ranges['lts/maintenance'].newestLts === undefined - ) { + if (ranges['lts/maintenance'].versions.length === 1) { ranges['lts/maintenance'].newestSecurity = - `${data[line].security.newest}`; - ranges['lts/maintenance'].newestLts = - `${data[line].support.lts.newest}`; + data[line].security.newest; + ranges['lts/maintenance'].newestLts = data[line].support.lts.newest; } if ( ranges['lts/maintenance'].oldestSecurity !== - `${data[line].security.oldest}` || + data[line].security.oldest || ranges['lts/maintenance'].oldestLts !== - `${data[line].support.lts.oldest}` + data[line].support.lts.oldest ) { ranges['lts/maintenance'].oldestSecurity = - `${data[line].security.oldest}`; - ranges['lts/maintenance'].oldestLts = - `${data[line].support.lts.oldest}`; + data[line].security.oldest; + ranges['lts/maintenance'].oldestLts = data[line].support.lts.oldest; } } } diff --git a/ranges/test/emptyFields.test.js b/ranges/test/emptyFields.test.js new file mode 100644 index 0000000..582295e --- /dev/null +++ b/ranges/test/emptyFields.test.js @@ -0,0 +1,126 @@ +const { deepStrictEqual } = require('node:assert'); +const ranges = require('../index'); +const { at } = require('./fixtures'); +const { describe, it } = require('node:test'); + +// core's snapshot time, when the Current line hasn't had a security release yet +const snapshotNow = JSON.parse(require('../../core/test/data/static/now.json')); + +describe('fields without a value should be undefined, not "undefined"', async () => { + it('should leave out the security fields of a Current line without security releases', async () => { + const data = await ranges(undefined, at(snapshotNow)); + + deepStrictEqual(data.current.versions, ['v25.1.0', 'v25.0.0']); + deepStrictEqual(data.current.oldestSecurity, undefined); + deepStrictEqual(data.current.newestSecurity, undefined); + deepStrictEqual(JSON.parse(JSON.stringify(data.current)), { + versions: ['v25.1.0', 'v25.0.0'], + newest: 'v25.1.0', + oldest: 'v25.0.0', + }); + }); + + it('should leave out the security fields of an LTS line without security releases', async () => { + // a date when the only Active LTS line hasn't had a security release + const data = await ranges(undefined, at('2016-06-01')); + + deepStrictEqual(data['lts/latest'].oldestSecurity, undefined); + deepStrictEqual(data['lts/latest'].newestSecurity, undefined); + deepStrictEqual(data['lts/latest'].oldestLts, '4.2.0'); + deepStrictEqual(data['lts/latest'].newestLts, '4.4.5'); + deepStrictEqual(data['lts/active'].newestSecurity, undefined); + }); + + it('should only take the security fields from the first Current line', async () => { + // a date when the newest Current line has no security releases, but an + // older line also in "current" (one with no LTS or maintenance dates in + // schedule.json) has one + const data = await ranges('current', at('2016-06-23')); + + deepStrictEqual(data.current.newest, 'v6.2.2'); + deepStrictEqual(data.current.versions.includes('v0.12.15'), true); + deepStrictEqual(data.current.oldestSecurity, undefined); + deepStrictEqual(data.current.newestSecurity, undefined); + }); + + it('should leave out the LTS and security fields when the data has none', async () => { + // made-up data, since the real data always has these by the time a line + // is LTS: neither line has security releases, and the newest release of + // each line isn't marked as LTS, so core has no `support.lts.newest` + const release = (version, date, lts = false) => ({ + version, + date, + lts, + files: ['linux-x64', 'src'], + }); + const index = [ + release('v99.1.0', '2099-11-01'), + release('v99.0.0', '2099-10-25', 'Placeholder'), + release('v98.1.0', '2098-11-01'), + release('v98.0.0', '2098-10-25', 'Placeholder'), + ]; + const schedule = { + v98: { + start: '2098-04-20', + lts: '2098-10-20', + maintenance: '2099-10-20', + end: '2101-04-30', + codename: '', + }, + v99: { + start: '2099-04-20', + lts: '2099-10-20', + maintenance: '2100-10-20', + end: '2102-04-30', + codename: '', + }, + }; + // round-tripping through JSON to check what ends up in the output + const data = JSON.parse( + JSON.stringify( + await ranges(undefined, at('2099-12-01', { index, schedule })), + ), + ); + + deepStrictEqual(data['lts/latest'], { + versions: ['v99.0.0'], + oldestLts: '99.0.0', + newest: 'v99.0.0', + oldest: 'v99.0.0', + }); + deepStrictEqual(data['lts/maintenance'], { + versions: ['v98.0.0'], + oldestLts: '98.0.0', + newest: 'v98.0.0', + oldest: 'v98.0.0', + }); + deepStrictEqual(data['lts/active'], { + versions: ['v99.0.0', 'v98.0.0'], + oldestLts: '98.0.0', + newest: 'v99.0.0', + oldest: 'v98.0.0', + }); + }); + + it('should never have "undefined" as a value', async () => { + for (const now of [ + '2016-06-01', + '2018-01-01', + '2020-01-01', + '2024-01-01', + '2025-09-01', + snapshotNow, + ]) { + const data = await ranges(undefined, at(now)); + for (const range in data) { + for (const field in data[range]) { + deepStrictEqual( + data[range][field] === 'undefined', + false, + `${now} ${range}.${field}`, + ); + } + } + } + }); +});