@astryxdesign/cli 0.6.3-canary.7ada42e → 0.6.3-canary.7ec7be0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -19
- package/api/build/_adapter.d.mts +50 -0
- package/api/build/_adapter.mjs +60 -0
- package/api/build/build.doc.mjs +13 -7
- package/api/build/build.test.mjs +184 -6
- package/api/build/build.type.d.mts +57 -2
- package/api/build/build.type.mjs +30 -7
- package/api/build/help/help.d.mts +4 -0
- package/api/build/help/help.mjs +24 -11
- package/api/build/kit/kit.d.mts +4 -1
- package/api/build/kit/kit.mjs +165 -49
- package/api/build/kit/rank.d.mts +44 -0
- package/api/build/kit/rank.mjs +432 -0
- package/api/build/kit/rank.test.mjs +196 -0
- package/api/component/_adapter.d.mts +6 -12
- package/api/component/_adapter.mjs +20 -10
- package/api/component/component.mjs +60 -12
- package/api/component/list/list.mjs +3 -2
- package/api/docs/_adapter.d.mts +251 -28
- package/api/docs/_adapter.mjs +918 -35
- package/api/docs/detail/detail.mjs +11 -3
- package/api/docs/detail/section/section.mjs +24 -13
- package/api/docs/detail/section/section.test.mjs +15 -6
- package/api/docs/docs.d.mts +5 -3
- package/api/docs/docs.doc.mjs +38 -17
- package/api/docs/docs.mjs +44 -8
- package/api/docs/docs.test.mjs +158 -4
- package/api/docs/docs.type.d.mts +181 -2
- package/api/docs/docs.type.mjs +117 -3
- package/api/docs/index/index.mjs +11 -3
- package/api/docs/index/index.test.mjs +1 -1
- package/api/docs/integration-tree.test.mjs +555 -0
- package/api/docs/integrationDocs.test.mjs +14 -14
- package/api/docs/list/list.mjs +25 -5
- package/api/docs/node/node.d.mts +43 -0
- package/api/docs/node/node.mjs +192 -0
- package/api/docs/reference-blocks.test.mjs +406 -0
- package/api/doctor/doctor.d.mts +30 -2
- package/api/doctor/doctor.mjs +151 -8
- package/api/doctor/doctor.test.mjs +95 -7
- package/api/index.d.mts +1 -1
- package/api/index.mjs +1 -0
- package/api/init/init.doc.mjs +1 -1
- package/api/integration/add-contribution.d.mts +2 -1
- package/api/integration/add-contribution.mjs +107 -5
- package/api/integration/add-contribution.test.mjs +137 -0
- package/api/integration/authoring-checks.mjs +138 -28
- package/api/integration/authoring-checks.test.mjs +171 -3
- package/api/integration/authoring-checks.type.mjs +6 -1
- package/api/integration/integration-authoring.type.d.mts +2 -0
- package/api/integration/integration-authoring.type.mjs +2 -0
- package/api/integration/integrationAdd.doc.mjs +6 -0
- package/api/integration/integrationAddDoc.doc.mjs +7 -1
- package/api/integration/integrationDocConflicts.doc.mjs +1 -1
- package/api/integration/integrationPackCheck.doc.mjs +1 -1
- package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +3 -0
- package/api/integration/pack-check.mjs +34 -0
- package/api/integration/pack-check.test.mjs +121 -0
- package/api/integration/pack-check.type.d.mts +26 -2
- package/api/integration/pack-check.type.mjs +14 -1
- package/api/integration/template-conflict-compatibility.test.mjs +73 -0
- package/api/integration/validate-integration.test.mjs +3 -3
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/index.ts +2 -0
- package/api/layout/_adapter.mjs +20 -5
- package/api/search/search.d.mts +59 -1
- package/api/search/search.doc.mjs +4 -3
- package/api/search/search.mjs +442 -71
- package/api/search/search.test.mjs +91 -2
- package/api/search/search.type.d.mts +13 -1
- package/api/search/search.type.mjs +4 -1
- package/api/template/list/list.mjs +1 -0
- package/api/template/table-floating-bulk-actions.test.mjs +66 -0
- package/api/template/template-integration.test.mjs +1072 -3
- package/api/template/template.doc.mjs +27 -7
- package/api/template/template.mjs +45 -8
- package/api/template/template.type.d.mts +6 -8
- package/api/template/template.type.mjs +3 -2
- package/api/theme/template/template.test.mjs +5 -0
- package/api/theme/themeTemplate.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +2 -2
- package/assets/docs/layout.doc.dense.mjs +2 -2
- package/assets/docs/layout.doc.mjs +1 -1
- package/assets/docs/principles.doc.mjs +6 -6
- package/assets/docs/styling-libraries.doc.mjs +3 -3
- package/assets/docs/styling.doc.mjs +4 -4
- package/assets/docs/theme.doc.mjs +4 -4
- package/assets/docs/tokens.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +30 -0
- package/assets/docs/tree/cli.doc.mjs +23 -0
- package/assets/docs/tree/commands.doc.mjs +25 -0
- package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +37 -9
- package/assets/docs/tree/integrations.test.mjs +62 -0
- package/assets/docs/tree/writing-docs.doc.mjs +286 -0
- package/assets/docs/working-with-ai.doc.mjs +3 -3
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
- package/authoring/debug/parse.d.mts +3 -3
- package/authoring/doctypes/_schema.d.mts +119 -117
- package/authoring/doctypes/_schema.mjs +54 -3
- package/authoring/doctypes/base/graph-fields.doc.mjs +7 -5
- package/authoring/doctypes/base/type.ts +6 -5
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +2 -2
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +3 -2
- package/authoring/doctypes/function/type.ts +3 -2
- package/authoring/doctypes/load-contract.test.mjs +26 -1
- package/authoring/doctypes/namespace/namespace.doc.mjs +6 -10
- package/authoring/doctypes/namespace/parse.test.mjs +23 -25
- package/authoring/doctypes/namespace/type.ts +5 -2
- package/authoring/doctypes/parse.mjs +3 -2
- package/authoring/doctypes/reference/reference.doc.mjs +34 -5
- package/authoring/doctypes/reference/type.ts +28 -11
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -1
- package/authoring/doctypes/template/parse.d.mts +2 -0
- package/authoring/doctypes/template/parse.mjs +4 -0
- package/authoring/doctypes/template/parse.test.mjs +18 -0
- package/authoring/doctypes/template/template.doc.mjs +6 -0
- package/authoring/doctypes/template/type.ts +8 -0
- package/authoring/identity/identity.doc.mjs +2 -2
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build.doc.mjs +11 -6
- package/clients/cli/commands/build.mjs +105 -67
- package/clients/cli/commands/build.text-fields.test.mjs +41 -0
- package/clients/cli/commands/component/index.mjs +1 -1
- package/clients/cli/commands/component-ownership.test.mjs +3 -3
- package/clients/cli/commands/discover.broken-integration.test.mjs +7 -8
- package/clients/cli/commands/discover.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +20 -8
- package/clients/cli/commands/docs.mjs +183 -69
- package/clients/cli/commands/docs.test.mjs +120 -16
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -2
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +14 -7
- package/clients/cli/commands/doctor-integration.test.mjs +75 -1
- package/clients/cli/commands/doctor.mjs +53 -16
- package/clients/cli/commands/integration-add.doc.mjs +10 -0
- package/clients/cli/commands/integration.mjs +1 -0
- package/clients/cli/commands/search.mjs +11 -2
- package/clients/cli/commands/setup-nudge.test.mjs +6 -0
- package/clients/cli/commands/template.doc.mjs +23 -6
- package/clients/cli/commands/template.mjs +4 -91
- package/clients/cli/index.mjs +8 -0
- package/clients/cli/lib/manifest.mjs +9 -2
- package/foundation/agent-docs/agent-docs.d.mts +3 -2
- package/foundation/agent-docs/agent-docs.mjs +13 -9
- package/foundation/agent-docs/agent-docs.test.mjs +19 -1
- package/foundation/config/project.d.mts +12 -11
- package/foundation/config/project.mjs +73 -59
- package/foundation/config/project.test.mjs +141 -5
- package/foundation/discovery/authoring-self-docs.d.mts +12 -0
- package/foundation/discovery/authoring-self-docs.mjs +20 -8
- package/foundation/discovery/authoring-self-docs.test.mjs +25 -9
- package/foundation/discovery/cli-self-docs.d.mts +40 -34
- package/foundation/discovery/cli-self-docs.mjs +91 -115
- package/foundation/discovery/cli-self-docs.test.mjs +104 -166
- package/foundation/discovery/component-discovery.d.mts +38 -0
- package/foundation/discovery/component-discovery.mjs +48 -0
- package/foundation/discovery/docs-discovery.d.mts +109 -9
- package/foundation/discovery/docs-discovery.mjs +223 -24
- package/foundation/discovery/docs-discovery.test.mjs +103 -27
- package/foundation/discovery/docs-output-budget.d.mts +2 -2
- package/foundation/discovery/docs-output-budget.mjs +1 -1
- package/foundation/discovery/docs-section-key.d.mts +28 -10
- package/foundation/discovery/docs-section-key.mjs +134 -33
- package/foundation/discovery/docs-section-key.test.mjs +41 -19
- package/foundation/discovery/template-adapter.d.mts +92 -6
- package/foundation/discovery/template-adapter.mjs +453 -17
- package/foundation/discovery/template-adapter.test.mjs +42 -0
- package/foundation/discovery/template-conflict-release.d.mts +13 -0
- package/foundation/discovery/template-conflict-release.mjs +40 -0
- package/foundation/discovery/template-conflict-release.test.mjs +40 -0
- package/foundation/doc-compiler/bundle.mjs +61 -1
- package/foundation/doc-compiler/bundle.test.mjs +21 -10
- package/foundation/doc-compiler/compile.d.mts +113 -132
- package/foundation/doc-compiler/compile.mjs +57 -10
- package/foundation/doc-compiler/diagnostics.mjs +28 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +2 -2
- package/foundation/doc-compiler/inputs.d.mts +2 -2
- package/foundation/doc-compiler/inputs.mjs +6 -1
- package/foundation/doc-compiler/ir.mjs +7 -0
- package/foundation/doc-compiler/lenses.d.mts +8 -5
- package/foundation/doc-compiler/lenses.mjs +50 -4
- package/foundation/doc-compiler/links.d.mts +162 -0
- package/foundation/doc-compiler/links.mjs +294 -0
- package/foundation/doc-compiler/links.test.mjs +192 -0
- package/foundation/doc-compiler/lower-doc.test.mjs +5 -2
- package/foundation/doc-compiler/read.d.mts +3 -2
- package/foundation/doc-compiler/read.mjs +6 -1
- package/foundation/doc-compiler/tree.d.mts +288 -0
- package/foundation/doc-compiler/tree.mjs +876 -0
- package/foundation/doc-compiler/tree.test.mjs +598 -0
- package/foundation/integrations/autolink.test.mjs +1 -1
- package/foundation/integrations/cli-requirement.d.mts +45 -0
- package/foundation/integrations/cli-requirement.mjs +154 -0
- package/foundation/integrations/cli-requirement.test.mjs +84 -0
- package/foundation/integrations/contribution-inventory.d.mts +2 -1
- package/foundation/integrations/contribution-inventory.mjs +10 -4
- package/foundation/integrations/contribution-inventory.test.mjs +19 -0
- package/foundation/integrations/integration-warnings.d.mts +9 -2
- package/foundation/integrations/integration-warnings.mjs +51 -26
- package/foundation/integrations/integration-warnings.test.mjs +74 -1
- package/foundation/integrations/validate-contributions.mjs +25 -27
- package/foundation/response/response-types.doc.d.mts +5 -1
- package/foundation/response/response-types.doc.mjs +19 -10
- package/foundation/text/string-utils.d.mts +8 -0
- package/foundation/text/string-utils.mjs +18 -0
- package/foundation/xle/expand.mjs +1 -1
- package/foundation/xle/xle.test.mjs +13 -0
- package/package.json +9 -9
- package/assets/docs/cli.doc.mjs +0 -15
|
@@ -74,16 +74,16 @@ describe('discoverBuiltinTopics', () => {
|
|
|
74
74
|
|
|
75
75
|
describe('discoverIntegrationDocs', () => {
|
|
76
76
|
it('contributes nothing when no docs root is declared or the root is gone', async () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
const nothing = {records: [], errors: [], namespaces: [], guides: []};
|
|
78
|
+
expect(await discoverIntegrationDocs({name: '@acme/widgets'})).toEqual(
|
|
79
|
+
nothing,
|
|
80
|
+
);
|
|
81
81
|
expect(
|
|
82
82
|
await discoverIntegrationDocs({
|
|
83
83
|
name: '@acme/widgets',
|
|
84
84
|
docs: path.join(tmpDir, 'nope'),
|
|
85
85
|
}),
|
|
86
|
-
).toEqual(
|
|
86
|
+
).toEqual(nothing);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
it('reads every topic under the root, with what it declares', async () => {
|
|
@@ -169,10 +169,7 @@ describe('discoverIntegrationDocs', () => {
|
|
|
169
169
|
id: 'steps',
|
|
170
170
|
title: 'Steps',
|
|
171
171
|
content: [
|
|
172
|
-
{
|
|
173
|
-
type: 'workflow',
|
|
174
|
-
steps: [{title: 'Install', description: 'Run install.'}],
|
|
175
|
-
},
|
|
172
|
+
{type: 'collection', source: {slot: 'guides'}},
|
|
176
173
|
],
|
|
177
174
|
},
|
|
178
175
|
],
|
|
@@ -180,12 +177,12 @@ describe('discoverIntegrationDocs', () => {
|
|
|
180
177
|
}),
|
|
181
178
|
);
|
|
182
179
|
expect(records).toEqual([]);
|
|
183
|
-
expect(errors[0].message).toContain('
|
|
180
|
+
expect(errors[0].message).toContain('Invalid discriminator value');
|
|
184
181
|
});
|
|
185
182
|
|
|
186
|
-
it('
|
|
187
|
-
const
|
|
188
|
-
integration('@acme/namespace', {
|
|
183
|
+
it('hands namespace docs and placed guides to the docs tree, by provider id', async () => {
|
|
184
|
+
const found = await discoverIntegrationDocs({
|
|
185
|
+
...integration('@acme/namespace', {
|
|
189
186
|
'guides.doc.mjs': {
|
|
190
187
|
type: 'namespace',
|
|
191
188
|
name: 'guides',
|
|
@@ -193,14 +190,56 @@ describe('discoverIntegrationDocs', () => {
|
|
|
193
190
|
summary: 'Every guide.',
|
|
194
191
|
slots: {guides: {title: 'Guides', accepts: {kinds: ['generic']}}},
|
|
195
192
|
},
|
|
193
|
+
'setup.doc.mjs': topic({
|
|
194
|
+
name: 'setup',
|
|
195
|
+
title: 'Set up',
|
|
196
|
+
placement: {parent: 'namespace:guides', slot: 'guides', order: 1},
|
|
197
|
+
}),
|
|
198
|
+
'deploying.doc.mjs': topic(),
|
|
199
|
+
}),
|
|
200
|
+
providerId: '@acme/tree-provider',
|
|
201
|
+
});
|
|
202
|
+
expect(found.errors).toEqual([]);
|
|
203
|
+
expect(found.records.map(record => record.name)).toEqual(['deploying']);
|
|
204
|
+
expect(found.records[0].providerId).toBe('@acme/tree-provider');
|
|
205
|
+
expect(found.namespaces).toMatchObject([
|
|
206
|
+
{
|
|
207
|
+
provider: '@acme/namespace',
|
|
208
|
+
providerId: '@acme/tree-provider',
|
|
209
|
+
source: '@acme/namespace/guides.doc.mjs',
|
|
210
|
+
doc: {name: 'guides'},
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
expect(found.guides).toMatchObject([
|
|
214
|
+
{
|
|
215
|
+
provider: '@acme/namespace',
|
|
216
|
+
providerId: '@acme/tree-provider',
|
|
217
|
+
kind: 'generic',
|
|
218
|
+
name: 'setup',
|
|
219
|
+
placement: {parent: 'namespace:guides', slot: 'guides', order: 1},
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('names what a namespace doc lacks, and refuses a placed guide that replaces a topic', async () => {
|
|
225
|
+
const {errors, namespaces, guides} = await discoverIntegrationDocs(
|
|
226
|
+
integration('@acme/namespace', {
|
|
227
|
+
'guides.doc.mjs': {type: 'namespace', name: 'guides', title: 'Guides'},
|
|
228
|
+
'setup.doc.mjs': topic({
|
|
229
|
+
name: 'setup',
|
|
230
|
+
replaces: 'getting-started',
|
|
231
|
+
placement: {parent: 'namespace:guides', slot: 'guides'},
|
|
232
|
+
}),
|
|
196
233
|
}),
|
|
197
234
|
);
|
|
198
|
-
expect(
|
|
199
|
-
expect(
|
|
200
|
-
expect(errors
|
|
201
|
-
|
|
235
|
+
expect(namespaces).toEqual([]);
|
|
236
|
+
expect(guides).toEqual([]);
|
|
237
|
+
expect(errors.map(error => error.message).join('\n')).toMatch(
|
|
238
|
+
/guides\.doc\.mjs.*slots/,
|
|
239
|
+
);
|
|
240
|
+
expect(errors.map(error => error.message).join('\n')).toContain(
|
|
241
|
+
'is placed in the docs tree and also declares `replaces`',
|
|
202
242
|
);
|
|
203
|
-
expect(errors[0].message).not.toContain('sections:');
|
|
204
243
|
});
|
|
205
244
|
});
|
|
206
245
|
|
|
@@ -271,6 +310,46 @@ describe('problemsInTopic', () => {
|
|
|
271
310
|
}
|
|
272
311
|
});
|
|
273
312
|
|
|
313
|
+
it('takes a reference block, and checks its projection and presentation', () => {
|
|
314
|
+
/** @param {any[]} content */
|
|
315
|
+
const withBlocks = content =>
|
|
316
|
+
problemsInTopic(topic({sections: [{title: 'Overview', content}]}));
|
|
317
|
+
expect(
|
|
318
|
+
withBlocks([
|
|
319
|
+
{type: 'reference', target: 'schema:integration'},
|
|
320
|
+
{
|
|
321
|
+
type: 'reference',
|
|
322
|
+
target: '@astryxdesign/cli:schema:integration',
|
|
323
|
+
projection: {fields: ['docs']},
|
|
324
|
+
presentation: 'compact',
|
|
325
|
+
},
|
|
326
|
+
]),
|
|
327
|
+
).toEqual([]);
|
|
328
|
+
expect(
|
|
329
|
+
withBlocks([
|
|
330
|
+
{type: 'reference', target: ''},
|
|
331
|
+
{type: 'reference', target: 'schema:x', presentation: 'card'},
|
|
332
|
+
{type: 'reference', target: 'schema:x', projection: {fields: []}},
|
|
333
|
+
{type: 'reference', target: 'schema:x', projection: {rows: ['a']}},
|
|
334
|
+
{type: 'reference', target: 'schema:x', projection: ['docs']},
|
|
335
|
+
{type: 'reference', target: 'schema:x', link: {}},
|
|
336
|
+
]),
|
|
337
|
+
).toEqual([
|
|
338
|
+
'sections[0].content[0].target: expected a non-empty string',
|
|
339
|
+
'sections[0].content[1].presentation: "card" is not one of summary, compact, full',
|
|
340
|
+
'sections[0].content[2].projection.fields: expected a non-empty array of names',
|
|
341
|
+
'sections[0].content[3].projection.rows: not a field of a projection',
|
|
342
|
+
'sections[0].content[4].projection: expected {fields?, sections?}, naming the parts of the doc to include',
|
|
343
|
+
'sections[0].content[5].link: not a field of a reference block',
|
|
344
|
+
]);
|
|
345
|
+
// Workflow and collection blocks stay in a namespace doc's `blocks`.
|
|
346
|
+
for (const type of ['workflow', 'collection']) {
|
|
347
|
+
expect(withBlocks([{type}]).join('\n')).toContain(
|
|
348
|
+
'requires the compiled graph renderer',
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
274
353
|
it('rejects a name that is not URL-safe', () => {
|
|
275
354
|
expect(problemsInTopic(topic({name: 'not a topic'})).join('\n')).toContain(
|
|
276
355
|
'URL-safe',
|
|
@@ -529,12 +608,10 @@ describe('problemsInTopic section keys', () => {
|
|
|
529
608
|
})),
|
|
530
609
|
});
|
|
531
610
|
|
|
532
|
-
it('
|
|
611
|
+
it('keeps legacy title-only key conflicts readable', () => {
|
|
533
612
|
expect(
|
|
534
613
|
problemsInTopic(doc([{title: 'Quick Start'}, {title: 'Quick-start'}])),
|
|
535
|
-
).toEqual([
|
|
536
|
-
expect.stringContaining('"quick-start" is already used by sections[0]'),
|
|
537
|
-
]);
|
|
614
|
+
).toEqual([]);
|
|
538
615
|
});
|
|
539
616
|
|
|
540
617
|
it('rejects an unsafe id', () => {
|
|
@@ -543,10 +620,8 @@ describe('problemsInTopic section keys', () => {
|
|
|
543
620
|
).toEqual([expect.stringContaining('is not a stable key')]);
|
|
544
621
|
});
|
|
545
622
|
|
|
546
|
-
it('
|
|
547
|
-
expect(problemsInTopic(doc([{title: '亮/暗模式'}]))).toEqual([
|
|
548
|
-
expect.stringContaining('Give the section an id'),
|
|
549
|
-
]);
|
|
623
|
+
it('keeps a legacy title that has no derived key readable', () => {
|
|
624
|
+
expect(problemsInTopic(doc([{title: '亮/暗模式'}]))).toEqual([]);
|
|
550
625
|
expect(
|
|
551
626
|
problemsInTopic(doc([{id: 'light-dark', title: '亮/暗模式'}])),
|
|
552
627
|
).toEqual([]);
|
|
@@ -580,7 +655,7 @@ describe('mergeTopic by section key', () => {
|
|
|
580
655
|
]);
|
|
581
656
|
});
|
|
582
657
|
|
|
583
|
-
it('
|
|
658
|
+
it('keeps a legacy title variant as a separate section', () => {
|
|
584
659
|
const merged = mergeTopic(base, {
|
|
585
660
|
sections: [
|
|
586
661
|
{title: 'Light-Dark Mode', content: [{type: 'prose', text: 'acme'}]},
|
|
@@ -588,6 +663,7 @@ describe('mergeTopic by section key', () => {
|
|
|
588
663
|
});
|
|
589
664
|
expect(titles(merged)).toEqual([
|
|
590
665
|
[null, 'Quick Start', 'base'],
|
|
666
|
+
[null, 'Light/Dark Mode', 'base'],
|
|
591
667
|
[null, 'Light-Dark Mode', 'acme'],
|
|
592
668
|
]);
|
|
593
669
|
});
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export function docPayloadBytes(payload: unknown): number;
|
|
11
11
|
/**
|
|
12
|
-
* @param {import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
12
|
+
* @param {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'> | import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
13
13
|
* @returns {number}
|
|
14
14
|
*/
|
|
15
|
-
export function docsIndexBytes(index: import("../../api/docs/docs.type.mjs").DocsIndex): number;
|
|
15
|
+
export function docsIndexBytes(index: Omit<import("../../api/docs/docs.type.mjs").DocsIndex, "links"> | import("../../api/docs/docs.type.mjs").DocsIndex): number;
|
|
16
16
|
/**
|
|
17
17
|
* The sections a single read would return more than `budget` bytes for.
|
|
18
18
|
* @param {any[]} sections
|
|
@@ -26,7 +26,7 @@ export function docPayloadBytes(payload) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* @param {import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
29
|
+
* @param {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'> | import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
30
30
|
* @returns {number}
|
|
31
31
|
*/
|
|
32
32
|
export function docsIndexBytes(index) {
|
|
@@ -31,9 +31,24 @@ export function sectionTitleKey(title: unknown): string;
|
|
|
31
31
|
*/
|
|
32
32
|
export function sectionKey(section: any): string;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* A name as a docs-tree route segment: lowercase words joined by hyphens, so
|
|
35
|
+
* `integrationPackCheck` and `integration pack` both read naturally.
|
|
36
|
+
* @param {string} name
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function routeSegment(name: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Fatal key problems: only an explicitly authored `id` can make a previously
|
|
42
|
+
* readable topic invalid. Derived-key collisions and titles with no Latin
|
|
43
|
+
* letters or digits are handled by {@link withSectionKeys} for 0.6.x
|
|
44
|
+
* compatibility.
|
|
45
|
+
* @param {any[]} sections
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
export function sectionKeyErrors(sections: any[]): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Compatibility notices for section keys. These inputs loaded in 0.6.x, so
|
|
51
|
+
* readers keep accepting them and assign deterministic fallback keys.
|
|
37
52
|
* @param {any[]} sections
|
|
38
53
|
* @returns {string[]}
|
|
39
54
|
*/
|
|
@@ -49,13 +64,16 @@ export function withSectionKeys<T extends {
|
|
|
49
64
|
sections: any[];
|
|
50
65
|
}>(doc: T): T;
|
|
51
66
|
/**
|
|
52
|
-
* Find the section a reader asked for
|
|
53
|
-
*
|
|
54
|
-
*
|
|
67
|
+
* Find the section a reader asked for. The 0.6.x first-match title-substring
|
|
68
|
+
* lookup remains first in the authored-title view. In a localized view, an
|
|
69
|
+
* exact stable key resolves before translated title substrings because those
|
|
70
|
+
* translations cannot participate in language-stable key allocation. Generated
|
|
71
|
+
* keys avoid authored-title conflicts, and authored IDs that would shadow an
|
|
72
|
+
* authored legacy query are rejected.
|
|
55
73
|
* @param {any[]} sections
|
|
56
74
|
* @param {string} query
|
|
57
|
-
* @returns {{section: any | null, candidates: any[]}} `candidates` lists
|
|
58
|
-
*
|
|
75
|
+
* @returns {{section: any | null, candidates: any[]}} `candidates` lists every
|
|
76
|
+
* legacy title match when the query is ambiguous
|
|
59
77
|
*/
|
|
60
78
|
export function findDocSection(sections: any[], query: string): {
|
|
61
79
|
section: any | null;
|
|
@@ -73,14 +91,14 @@ export function sectionSummary(section: any, max?: number): string;
|
|
|
73
91
|
* The index a topic-only read returns: what the topic is, and one entry per
|
|
74
92
|
* section with the key to read it by.
|
|
75
93
|
* @param {{name: string, title: string, description: string, sections: any[]}} doc
|
|
76
|
-
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
94
|
+
* @returns {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'>}
|
|
77
95
|
*/
|
|
78
96
|
export function buildDocsIndexData(doc: {
|
|
79
97
|
name: string;
|
|
80
98
|
title: string;
|
|
81
99
|
description: string;
|
|
82
100
|
sections: any[];
|
|
83
|
-
}): import("../../api/docs/docs.type.mjs").DocsIndex
|
|
101
|
+
}): Omit<import("../../api/docs/docs.type.mjs").DocsIndex, "links">;
|
|
84
102
|
/**
|
|
85
103
|
* @file Stable section keys, section lookup, and the topic index.
|
|
86
104
|
*
|
|
@@ -34,7 +34,10 @@ const SOURCE_TITLE = Symbol('astryx.docs.sourceTitle');
|
|
|
34
34
|
* @returns {T}
|
|
35
35
|
*/
|
|
36
36
|
export function withSourceTitle(section, title) {
|
|
37
|
-
Object.defineProperty(section, SOURCE_TITLE, {
|
|
37
|
+
Object.defineProperty(section, SOURCE_TITLE, {
|
|
38
|
+
value: title,
|
|
39
|
+
configurable: true,
|
|
40
|
+
});
|
|
38
41
|
return section;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -77,9 +80,77 @@ export function sectionKey(section) {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
+
* The first section the 0.6.x title-substring lookup would return for a query.
|
|
84
|
+
* @param {any[]} sections
|
|
85
|
+
* @param {string} query
|
|
86
|
+
* @returns {number}
|
|
87
|
+
*/
|
|
88
|
+
function legacyTitleMatchIndex(sections, query) {
|
|
89
|
+
const lower = query.toLowerCase();
|
|
90
|
+
return sections.findIndex(section => {
|
|
91
|
+
const title = sourceTitle(section);
|
|
92
|
+
return typeof title === 'string' && title.toLowerCase().includes(lower);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A name as a docs-tree route segment: lowercase words joined by hyphens, so
|
|
98
|
+
* `integrationPackCheck` and `integration pack` both read naturally.
|
|
99
|
+
* @param {string} name
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
export function routeSegment(name) {
|
|
103
|
+
return String(name)
|
|
104
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
107
|
+
.replace(/^-+|-+$/g, '');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fatal key problems: only an explicitly authored `id` can make a previously
|
|
112
|
+
* readable topic invalid. Derived-key collisions and titles with no Latin
|
|
113
|
+
* letters or digits are handled by {@link withSectionKeys} for 0.6.x
|
|
114
|
+
* compatibility.
|
|
115
|
+
* @param {any[]} sections
|
|
116
|
+
* @returns {string[]}
|
|
117
|
+
*/
|
|
118
|
+
export function sectionKeyErrors(sections) {
|
|
119
|
+
/** @type {string[]} */
|
|
120
|
+
const problems = [];
|
|
121
|
+
/** @type {Map<string, number>} */
|
|
122
|
+
const seen = new Map();
|
|
123
|
+
sections.forEach((section, s) => {
|
|
124
|
+
if (section?.id == null) return;
|
|
125
|
+
const at = `sections[${s}]`;
|
|
126
|
+
if (typeof section.id !== 'string' || !SECTION_KEY_RE.test(section.id)) {
|
|
127
|
+
problems.push(
|
|
128
|
+
`${at}.id: ${JSON.stringify(section.id)} is not a stable key. Use lowercase letters and digits joined by single hyphens.`,
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const first = seen.get(section.id);
|
|
133
|
+
if (first != null) {
|
|
134
|
+
problems.push(
|
|
135
|
+
`${at}: the key "${section.id}" is already used by sections[${first}]. Give one of them a distinct id.`,
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const legacyOwner = legacyTitleMatchIndex(sections, section.id);
|
|
140
|
+
if (legacyOwner !== -1 && legacyOwner !== s) {
|
|
141
|
+
problems.push(
|
|
142
|
+
`${at}.id: the key "${section.id}" is already a legacy title query for sections[${legacyOwner}]. Choose an id that resolves to this section.`,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
seen.set(section.id, s);
|
|
147
|
+
});
|
|
148
|
+
return problems;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Compatibility notices for section keys. These inputs loaded in 0.6.x, so
|
|
153
|
+
* readers keep accepting them and assign deterministic fallback keys.
|
|
83
154
|
* @param {any[]} sections
|
|
84
155
|
* @returns {string[]}
|
|
85
156
|
*/
|
|
@@ -90,7 +161,6 @@ export function sectionKeyProblems(sections) {
|
|
|
90
161
|
const seen = new Map();
|
|
91
162
|
sections.forEach((section, s) => {
|
|
92
163
|
const at = `sections[${s}]`;
|
|
93
|
-
// A missing title is reported where titles are checked; no key to judge.
|
|
94
164
|
if (
|
|
95
165
|
section?.id == null &&
|
|
96
166
|
(typeof section?.title !== 'string' || section.title === '')
|
|
@@ -109,14 +179,14 @@ export function sectionKeyProblems(sections) {
|
|
|
109
179
|
const key = sectionKey(section);
|
|
110
180
|
if (key === '') {
|
|
111
181
|
problems.push(
|
|
112
|
-
`${at}: no key derives from the title ${JSON.stringify(section?.title)}.
|
|
182
|
+
`${at}: no key derives from the title ${JSON.stringify(section?.title)}. A deterministic section-N compatibility key is used; add an explicit id.`,
|
|
113
183
|
);
|
|
114
184
|
return;
|
|
115
185
|
}
|
|
116
186
|
const first = seen.get(key);
|
|
117
187
|
if (first != null) {
|
|
118
188
|
problems.push(
|
|
119
|
-
`${at}: the key "${key}" is already used by sections[${first}].
|
|
189
|
+
`${at}: the key "${key}" is already used by sections[${first}]. A suffixed compatibility key is used for title-derived collisions; give one of them a distinct id or title.`,
|
|
120
190
|
);
|
|
121
191
|
return;
|
|
122
192
|
}
|
|
@@ -133,48 +203,79 @@ export function sectionKeyProblems(sections) {
|
|
|
133
203
|
* @returns {T}
|
|
134
204
|
*/
|
|
135
205
|
export function withSectionKeys(doc) {
|
|
206
|
+
const reserved = new Set(
|
|
207
|
+
doc.sections
|
|
208
|
+
.map(section => section?.id)
|
|
209
|
+
.filter(id => typeof id === 'string' && SECTION_KEY_RE.test(id)),
|
|
210
|
+
);
|
|
211
|
+
const used = new Set();
|
|
136
212
|
return {
|
|
137
213
|
...doc,
|
|
138
|
-
sections: doc.sections.map(section =>
|
|
139
|
-
section.id != null
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
214
|
+
sections: doc.sections.map((section, index) => {
|
|
215
|
+
if (section.id != null) {
|
|
216
|
+
used.add(section.id);
|
|
217
|
+
return section;
|
|
218
|
+
}
|
|
219
|
+
const base =
|
|
220
|
+
sectionTitleKey(sourceTitle(section)) || `section-${index + 1}`;
|
|
221
|
+
let key = base;
|
|
222
|
+
let suffix = 2;
|
|
223
|
+
while (
|
|
224
|
+
reserved.has(key) ||
|
|
225
|
+
used.has(key) ||
|
|
226
|
+
(legacyTitleMatchIndex(doc.sections, key) !== -1 &&
|
|
227
|
+
legacyTitleMatchIndex(doc.sections, key) !== index)
|
|
228
|
+
) {
|
|
229
|
+
key = `${base}-${suffix}`;
|
|
230
|
+
suffix += 1;
|
|
231
|
+
}
|
|
232
|
+
used.add(key);
|
|
233
|
+
return withSourceTitle({...section, id: key}, sourceTitle(section));
|
|
234
|
+
}),
|
|
146
235
|
};
|
|
147
236
|
}
|
|
148
237
|
|
|
149
238
|
/**
|
|
150
|
-
* Find the section a reader asked for
|
|
151
|
-
*
|
|
152
|
-
*
|
|
239
|
+
* Find the section a reader asked for. The 0.6.x first-match title-substring
|
|
240
|
+
* lookup remains first in the authored-title view. In a localized view, an
|
|
241
|
+
* exact stable key resolves before translated title substrings because those
|
|
242
|
+
* translations cannot participate in language-stable key allocation. Generated
|
|
243
|
+
* keys avoid authored-title conflicts, and authored IDs that would shadow an
|
|
244
|
+
* authored legacy query are rejected.
|
|
153
245
|
* @param {any[]} sections
|
|
154
246
|
* @param {string} query
|
|
155
|
-
* @returns {{section: any | null, candidates: any[]}} `candidates` lists
|
|
156
|
-
*
|
|
247
|
+
* @returns {{section: any | null, candidates: any[]}} `candidates` lists every
|
|
248
|
+
* legacy title match when the query is ambiguous
|
|
157
249
|
*/
|
|
158
250
|
export function findDocSection(sections, query) {
|
|
159
251
|
const wanted = query.trim();
|
|
160
252
|
const byKey = sections.find(section => sectionKey(section) === wanted);
|
|
161
|
-
|
|
253
|
+
const hasLocalizedTitles = sections.some(
|
|
254
|
+
section => sourceTitle(section) !== section.title,
|
|
255
|
+
);
|
|
256
|
+
if (byKey && hasLocalizedTitles) {
|
|
257
|
+
return {section: byKey, candidates: []};
|
|
258
|
+
}
|
|
162
259
|
|
|
163
260
|
const lower = wanted.toLowerCase();
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
section =>
|
|
167
|
-
section.title.toLowerCase() === lower ||
|
|
168
|
-
(derived !== '' && sectionTitleKey(sourceTitle(section)) === derived),
|
|
261
|
+
const legacy = sections.filter(section =>
|
|
262
|
+
section.title.toLowerCase().includes(lower),
|
|
169
263
|
);
|
|
170
|
-
if (
|
|
171
|
-
|
|
264
|
+
if (legacy.length > 0) {
|
|
265
|
+
return {section: legacy[0], candidates: legacy.length > 1 ? legacy : []};
|
|
266
|
+
}
|
|
172
267
|
|
|
173
|
-
const
|
|
174
|
-
section
|
|
268
|
+
const byFallbackKey = sections.find(
|
|
269
|
+
section => sectionKey(section) === wanted,
|
|
270
|
+
);
|
|
271
|
+
if (byFallbackKey) return {section: byFallbackKey, candidates: []};
|
|
272
|
+
|
|
273
|
+
const derived = sectionTitleKey(wanted);
|
|
274
|
+
const byDerivedKey = sections.find(
|
|
275
|
+
section =>
|
|
276
|
+
derived !== '' && sectionTitleKey(sourceTitle(section)) === derived,
|
|
175
277
|
);
|
|
176
|
-
|
|
177
|
-
return {section: null, candidates: partial};
|
|
278
|
+
return {section: byDerivedKey ?? null, candidates: []};
|
|
178
279
|
}
|
|
179
280
|
|
|
180
281
|
/**
|
|
@@ -205,7 +306,7 @@ export function sectionSummary(section, max = SECTION_SUMMARY_MAX) {
|
|
|
205
306
|
* The index a topic-only read returns: what the topic is, and one entry per
|
|
206
307
|
* section with the key to read it by.
|
|
207
308
|
* @param {{name: string, title: string, description: string, sections: any[]}} doc
|
|
208
|
-
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
309
|
+
* @returns {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'>}
|
|
209
310
|
*/
|
|
210
311
|
export function buildDocsIndexData(doc) {
|
|
211
312
|
return {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
buildDocsIndexData,
|
|
8
8
|
findDocSection,
|
|
9
9
|
sectionKey,
|
|
10
|
+
sectionKeyErrors,
|
|
10
11
|
sectionKeyProblems,
|
|
11
12
|
sectionSummary,
|
|
12
13
|
sectionTitleKey,
|
|
@@ -68,12 +69,17 @@ describe('sectionKeyProblems', () => {
|
|
|
68
69
|
section('Quick-start'),
|
|
69
70
|
]);
|
|
70
71
|
expect(problems).toHaveLength(1);
|
|
71
|
-
expect(problems[0]).toContain(
|
|
72
|
+
expect(problems[0]).toContain(
|
|
73
|
+
'"quick-start" is already used by sections[0]',
|
|
74
|
+
);
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
it('rejects an authored id that collides with a derived key', () => {
|
|
75
78
|
expect(
|
|
76
|
-
sectionKeyProblems([
|
|
79
|
+
sectionKeyProblems([
|
|
80
|
+
section('Install'),
|
|
81
|
+
section('Setup', {id: 'install'}),
|
|
82
|
+
]),
|
|
77
83
|
).toHaveLength(1);
|
|
78
84
|
});
|
|
79
85
|
|
|
@@ -85,13 +91,13 @@ describe('sectionKeyProblems', () => {
|
|
|
85
91
|
}
|
|
86
92
|
});
|
|
87
93
|
|
|
88
|
-
it('
|
|
94
|
+
it('reports a title no key derives from', () => {
|
|
89
95
|
expect(sectionKeyProblems([section('亮/暗模式')])[0]).toContain(
|
|
90
|
-
'
|
|
91
|
-
);
|
|
92
|
-
expect(sectionKeyProblems([section('亮/暗模式', {id: 'light-dark'})])).toEqual(
|
|
93
|
-
[],
|
|
96
|
+
'compatibility key is used',
|
|
94
97
|
);
|
|
98
|
+
expect(
|
|
99
|
+
sectionKeyProblems([section('亮/暗模式', {id: 'light-dark'})]),
|
|
100
|
+
).toEqual([]);
|
|
95
101
|
});
|
|
96
102
|
|
|
97
103
|
it('leaves a missing title to the title check', () => {
|
|
@@ -107,12 +113,21 @@ describe('withSectionKeys', () => {
|
|
|
107
113
|
expect(doc.sections.map(s => s.id)).toEqual(['quick-start', 'use']);
|
|
108
114
|
});
|
|
109
115
|
|
|
110
|
-
it('keeps
|
|
116
|
+
it('keeps authored titles and keys stable for translated sections', () => {
|
|
111
117
|
const doc = withSectionKeys({
|
|
112
|
-
sections: [
|
|
118
|
+
sections: [
|
|
119
|
+
withSourceTitle(section('高级安装'), 'Install advanced'),
|
|
120
|
+
withSourceTitle(section('安装'), 'Install'),
|
|
121
|
+
],
|
|
113
122
|
});
|
|
114
|
-
expect(doc.sections
|
|
115
|
-
|
|
123
|
+
expect(doc.sections.map(s => s.id)).toEqual([
|
|
124
|
+
'install-advanced',
|
|
125
|
+
'install-2',
|
|
126
|
+
]);
|
|
127
|
+
for (const target of doc.sections) {
|
|
128
|
+
expect(findDocSection(doc.sections, target.id).section).toBe(target);
|
|
129
|
+
}
|
|
130
|
+
expect(sourceTitle(doc.sections[0])).toBe('Install advanced');
|
|
116
131
|
});
|
|
117
132
|
});
|
|
118
133
|
|
|
@@ -127,7 +142,14 @@ describe('findDocSection', () => {
|
|
|
127
142
|
],
|
|
128
143
|
}).sections;
|
|
129
144
|
|
|
130
|
-
it('finds
|
|
145
|
+
it('finds every emitted key without changing a legacy query', () => {
|
|
146
|
+
const colliding = withSectionKeys({
|
|
147
|
+
sections: [section('Install advanced'), section('Install')],
|
|
148
|
+
}).sections;
|
|
149
|
+
expect(findDocSection(colliding, 'install').section).toBe(colliding[0]);
|
|
150
|
+
for (const target of colliding) {
|
|
151
|
+
expect(findDocSection(colliding, target.id).section).toBe(target);
|
|
152
|
+
}
|
|
131
153
|
expect(findDocSection(sections, 'tokens').section.title).toBe(
|
|
132
154
|
'Theme Tokens',
|
|
133
155
|
);
|
|
@@ -149,15 +171,15 @@ describe('findDocSection', () => {
|
|
|
149
171
|
expect(findDocSection(sections, 'props').section.id).toBe('theme-props');
|
|
150
172
|
});
|
|
151
173
|
|
|
152
|
-
it('
|
|
174
|
+
it('keeps the first ambiguous exact-title match and lists the candidates', () => {
|
|
153
175
|
const {section: match, candidates} = findDocSection(sections, 'Overview');
|
|
154
|
-
expect(match).
|
|
176
|
+
expect(match?.id).toBe('overview-a');
|
|
155
177
|
expect(candidates.map(s => s.id)).toEqual(['overview-a', 'overview-b']);
|
|
156
178
|
});
|
|
157
179
|
|
|
158
|
-
it('
|
|
180
|
+
it('keeps the first ambiguous partial-title match', () => {
|
|
159
181
|
const {section: match, candidates} = findDocSection(sections, 'theme');
|
|
160
|
-
expect(match).
|
|
182
|
+
expect(match?.id).toBe('theme-props');
|
|
161
183
|
expect(candidates.map(s => s.id)).toEqual(['theme-props', 'tokens']);
|
|
162
184
|
});
|
|
163
185
|
|
|
@@ -185,9 +207,9 @@ describe('sectionSummary', () => {
|
|
|
185
207
|
});
|
|
186
208
|
|
|
187
209
|
it('is empty when the section has no text', () => {
|
|
188
|
-
expect(
|
|
189
|
-
'',
|
|
190
|
-
);
|
|
210
|
+
expect(
|
|
211
|
+
sectionSummary({content: [{type: 'table', headers: [], rows: []}]}),
|
|
212
|
+
).toBe('');
|
|
191
213
|
});
|
|
192
214
|
|
|
193
215
|
it('cuts a long summary at a word boundary', () => {
|