diff --git a/docs-app/public/docs/meta.jsonc b/docs-app/public/docs/meta.jsonc index 88a15d88..187770af 100644 --- a/docs-app/public/docs/meta.jsonc +++ b/docs-app/public/docs/meta.jsonc @@ -1,5 +1,5 @@ { // We want usage to be first, which breaks the // default sort order provided by the filesystem: Alphabetical - "order": ["usage", "components", "plugins"], + "order": ["usage", "plugins"], } diff --git a/docs-app/public/docs/usage/meta.jsonc b/docs-app/public/docs/usage/meta.jsonc index 711bf244..9062af7b 100644 --- a/docs-app/public/docs/usage/meta.jsonc +++ b/docs-app/public/docs/usage/meta.jsonc @@ -1,4 +1,4 @@ { // This is in order of need-to-know - "order": ["setup", "rendering-pages", "ordering-pages"], + "order": ["setup", "rendering-pages", "ordering-pages", "testing"], } diff --git a/fixtures/discover/c/index.md b/fixtures/discover/c/index.md new file mode 100644 index 00000000..e69de29b diff --git a/fixtures/discover/c/meta.jsonc b/fixtures/discover/c/meta.jsonc index beb3dc1f..3092b354 100644 --- a/fixtures/discover/c/meta.jsonc +++ b/fixtures/discover/c/meta.jsonc @@ -1,3 +1,3 @@ { - "order": ["c-b", "c-a"], + "order": ["c-b", "c-a", "d", "e"], } diff --git a/fixtures/discover/meta.jsonc b/fixtures/discover/meta.jsonc index 798e090f..dba55c98 100644 --- a/fixtures/discover/meta.jsonc +++ b/fixtures/discover/meta.jsonc @@ -1,3 +1,3 @@ { - "order": ["b", "a"], + "order": ["c"], } diff --git a/package.json b/package.json index 5418c418..fa8a5592 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,8 @@ "build:declarations": "tsc --declaration", "_syncPnpm": "pnpm sync-dependencies-meta-injected", "start:typedoc": "typedoc --options ./typedoc.config.json --watch", - "test:node": "vitest --run" + "test:node": "vitest --run", + "test:dev": "vitest dev" }, "peerDependencies": { "@ember/test-waiters": "^3.1.0", diff --git a/src/plugins/markdown-pages/discover.test.ts b/src/plugins/markdown-pages/discover.test.ts index 7708e4a7..1a3b264d 100644 --- a/src/plugins/markdown-pages/discover.test.ts +++ b/src/plugins/markdown-pages/discover.test.ts @@ -15,13 +15,19 @@ describe('discover', () => { expect(result.groups[0]?.tree).toMatchInlineSnapshot(` { - "first": "/c/c-b.md", + "first": "/c/index.md", "name": "root", "pages": [ { - "first": "/c/c-b.md", + "first": "/c/index.md", "name": "c", "pages": [ + { + "cleanedName": "index", + "groupName": "c", + "name": "index", + "path": "/c/index.md", + }, { "cleanedName": "c b", "groupName": "c", diff --git a/src/plugins/markdown-pages/sort.applyPredestinedOrder.test.ts b/src/plugins/markdown-pages/sort.applyPredestinedOrder.test.ts index 380e0e10..60cfe344 100644 --- a/src/plugins/markdown-pages/sort.applyPredestinedOrder.test.ts +++ b/src/plugins/markdown-pages/sort.applyPredestinedOrder.test.ts @@ -4,14 +4,14 @@ import { applyPredestinedOrder } from './sort.js'; describe('applyPredestinedOrder', () => { test('it works', () => { - expect(applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c'])).deep.equal(['a', 'c', 'b']); + expect(applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c', 'b'])).deep.equal(['a', 'c', 'b']); }); test('it works with objects', () => { expect( applyPredestinedOrder( [{ name: 'c' }, { name: 'b' }, { name: 'a' }], - ['a', 'c'], + ['a', 'c', 'b'], (item) => item.name ) ).deep.equal([{ name: 'a' }, { name: 'c' }, { name: 'b' }]); @@ -21,19 +21,34 @@ describe('applyPredestinedOrder', () => { expect(applyPredestinedOrder(['c', 'a'], ['a', 'c'])).deep.equal(['a', 'c']); }); - test('it works when the order specifies things that do not exist', () => { - expect(applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c', 'x', 'y', 'z'])).deep.equal([ - 'a', - 'c', - 'b', - ]); + test('it handles index pages as always first', () => { + expect(applyPredestinedOrder(['c', 'a', 'index'], ['a', 'c'])).deep.equal(['index', 'a', 'c']); }); - test('it works when the order specifies duplicates', () => { - expect(applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c', 'a', 'c'])).deep.equal([ - 'a', - 'c', - 'b', - ]); + test('it errors when the order specifies things that do not exist', () => { + expect(() => applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c', 'x'])).toThrow( + 'Order configuration specified "x" but it was not found in the list. Pages are ["c","b","a"]' + ); + }); + + test('it errors when order is a different length than list', () => { + expect(() => applyPredestinedOrder(['c', 'b', 'a', 'd'], ['a', 'c', 'b'])) + .toThrow(`Order configuration specified different number of arguments than available pages: +Order: ["a","c","b"] +Actual: ["c","b","a","d"]`); + }); + + test('it errors if duplicate pages are specified', () => { + expect(() => applyPredestinedOrder(['c', 'b', 'a'], ['a', 'c', 'a', 'c'])) + .toThrow(`Order configuration specified duplicate pages: +Unique: ["a","c"] +Order: ["a","c","a","c"] +Actual: ["c","b","a"]`); + }); + + test('it errors if a configuration order option is empty', () => { + expect(() => applyPredestinedOrder(['c', 'b', 'a'], ['a', '', 'd'])).toThrow( + `Order configuration found an empty string at index 1` + ); }); }); diff --git a/src/plugins/markdown-pages/sort.js b/src/plugins/markdown-pages/sort.js index 40b67d10..ce7d72bf 100644 --- a/src/plugins/markdown-pages/sort.js +++ b/src/plugins/markdown-pages/sort.js @@ -1,3 +1,19 @@ +/** + * @param {unknown[]} arr + * @returns {unknown[]} + */ +function uniq(arr) { + return Array.from(new Set(arr)); +} + +/** + * @param {string} path + * @returns {string} + */ +function findPathForJsonc(path) { + return path.replace(/\/meta\.jsonc?$/, ''); +} + /** * Tutorials (and groups) are all 123-name * This is so that we can sort them manually on the file system. @@ -49,21 +65,39 @@ export function betterSort(property) { * @param {Item[]} list * @param {string[]} order * @param {(item: Item) => any} [ find ] + * @throws {Error} * @returns {Item[]} */ export function applyPredestinedOrder(list, order, find = (x) => x) { - let result = []; + let indexPage = list.find((x) => find(x) === 'index'); + let result = indexPage ? [indexPage] : []; + + list = list.filter((a) => a !== indexPage); + + let remaining = [...list]; - let remaining = list; + if (uniq(order).length !== order.length) + throw new Error(`Order configuration specified duplicate pages: +Unique: ${JSON.stringify(uniq(order))} +Order: ${JSON.stringify(order)} +Actual: ${JSON.stringify(list.map(find))}`); - for (let i = 0; i < Math.max(list.length, order.length); i++) { + if (order.length !== list.length) + throw new Error(`Order configuration specified different number of arguments than available pages: +Order: ${JSON.stringify(order)} +Actual: ${JSON.stringify(list.map(find))}`); + + for (let i = 0; i < order.length; i++) { let current = order[i]; - if (!current) break; + if (!current) throw new Error(`Order configuration found an empty string at index ${i}`); let foundIndex = remaining.findIndex((x) => find(x) === current); - if (foundIndex < 0) continue; + if (foundIndex < 0) + throw new Error( + `Order configuration specified "${current}" but it was not found in the list. Pages are ${JSON.stringify(list.map(find))}` + ); // remove let [found] = remaining.splice(foundIndex, 1); @@ -73,7 +107,11 @@ export function applyPredestinedOrder(list, order, find = (x) => x) { result.push(found); } - result.push(...remaining); + if (remaining.length > 0) { + throw new Error( + `Order configuration did not specify all pages. Remaining pages are ${JSON.stringify(remaining.map(find))}` + ); + } return result; } @@ -93,16 +131,22 @@ export function sortTree(tree, configs, parents = []) { if (configs.length > 0) { let subPath = `${[...parents, tree.name].join('/')}`; - let config = configs.filter(Boolean).find((config) => config.path.startsWith(subPath))?.config; + let config = configs + .filter(Boolean) + .find((config) => findPathForJsonc(config.path) === subPath)?.config; if (!config?.order) { return tree; } // Should the name always avoid the extension? - let replacementPages = applyPredestinedOrder(tree.pages, config.order, (page) => page.name); + try { + let replacementPages = applyPredestinedOrder(tree.pages, config.order, (page) => page.name); - tree.pages = replacementPages; + tree.pages = replacementPages; + } catch (error) { + throw new Error(`Error while sorting tree at ${subPath}.\n${error}`); + } } return tree; diff --git a/src/plugins/markdown-pages/sort.sortTree.test.ts b/src/plugins/markdown-pages/sort.sortTree.test.ts index 38c10e32..e9314fdb 100644 --- a/src/plugins/markdown-pages/sort.sortTree.test.ts +++ b/src/plugins/markdown-pages/sort.sortTree.test.ts @@ -70,20 +70,20 @@ describe('sortTree', () => { name: 'top', pages: [ { - name: 'second', + name: 'child', pages: [ { - path: '/top/second/second', + path: '/top/child/second', name: 'second', groupName: 'top', cleanedName: 'second', }, - { name: 'first', path: '/top/second/first', groupName: 'top', cleanedName: 'first' }, + { name: 'first', path: '/top/child/first', groupName: 'top', cleanedName: 'first' }, ], }, ], }, - [{ path: 'top/second/meta.jsonc', config: { order: ['first', 'second'] } }] + [{ path: 'top/child/meta.jsonc', config: { order: ['first', 'second'] } }] ); expect(result.pages.length).toEqual(1);