@astryxdesign/cli 0.7.2-canary.e2909cf → 0.7.2-canary.ebcc404
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/api/component/_adapter.d.mts +27 -1
- package/api/component/_adapter.mjs +45 -7
- package/api/component/component.mjs +12 -3
- package/api/component/integration-export-name.test.mjs +419 -0
- package/api/gap-report/gap-report.mjs +66 -4
- package/api/integration/pack-check.mjs +3 -94
- package/api/integration/pack-check.test.mjs +112 -0
- package/api/search/search.d.mts +9 -0
- package/api/search/search.mjs +82 -7
- package/api/swizzle/copy/copy.mjs +28 -14
- package/assets/docs/tree/component-imports.doc.mjs +8 -0
- package/authoring/doctypes/component/component.doc.mjs +1 -1
- package/authoring/doctypes/component/type.ts +6 -3
- package/foundation/discovery/component-discovery.d.mts +83 -6
- package/foundation/discovery/component-discovery.export-names.test.mjs +293 -0
- package/foundation/discovery/component-discovery.mjs +313 -49
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +20 -0
- package/foundation/discovery/module-exports.d.mts +21 -0
- package/foundation/discovery/module-exports.mjs +343 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/package.json +9 -9
|
@@ -115,19 +115,36 @@ export function discoverExternalComponentsGrouped(docsDir: string): Record<strin
|
|
|
115
115
|
* @returns {string | null}
|
|
116
116
|
*/
|
|
117
117
|
export function findExternalComponentDoc(docsDir: string, name: string): string | null;
|
|
118
|
+
/** @param {string} docPath */
|
|
119
|
+
export function docStem(docPath: string): string;
|
|
120
|
+
/**
|
|
121
|
+
* Visit every component doc under an integration's components root, hidden
|
|
122
|
+
* ones included, in discovery order, with its stem.
|
|
123
|
+
* @param {string | undefined} componentsDir
|
|
124
|
+
* @param {(docPath: string, stem: string) => void} visit
|
|
125
|
+
*/
|
|
126
|
+
export function forEachIntegrationDoc(componentsDir: string | undefined, visit: (docPath: string, stem: string) => void): void;
|
|
118
127
|
/**
|
|
119
128
|
* Discover ownership records for the components contributed by a single loaded
|
|
120
129
|
* integration. Scans the integration's resolved `components` dir for same-stem
|
|
121
|
-
* doc files and records each with owner package + issuesUrl + sourcePath.
|
|
130
|
+
* doc files and records each with owner package + issuesUrl + sourcePath. A doc
|
|
131
|
+
* that names a differently named export of its file is recorded under that
|
|
132
|
+
* export unless the name is taken: every doc's file name (a hidden doc's too),
|
|
133
|
+
* a Core name a doc in the package replaces, and a name an earlier doc records.
|
|
122
134
|
*
|
|
123
135
|
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
124
136
|
* a single entry from `loadedIntegrations` (lib/integrations.mjs)
|
|
137
|
+
* @param {{documentedNames?: boolean}} [options] `documentedNames: false`
|
|
138
|
+
* records each doc under its file name alone, without reading the names
|
|
139
|
+
* docs document: for a caller that needs only what each doc says
|
|
125
140
|
* @returns {Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>}
|
|
126
141
|
*/
|
|
127
142
|
export function discoverIntegrationComponents(integration: {
|
|
128
143
|
name: string;
|
|
129
144
|
components?: string;
|
|
130
145
|
issuesUrl?: string;
|
|
146
|
+
}, { documentedNames }?: {
|
|
147
|
+
documentedNames?: boolean;
|
|
131
148
|
}): Array<{
|
|
132
149
|
name: string;
|
|
133
150
|
package: string;
|
|
@@ -141,9 +158,12 @@ export function discoverIntegrationComponents(integration: {
|
|
|
141
158
|
* Invalid metadata or a missing same-stem source removes only that component;
|
|
142
159
|
* callers can report the returned errors while retaining valid siblings. A
|
|
143
160
|
* component whose doc sets `replaces` carries the value as written; the
|
|
144
|
-
* component replacement resolver decides what it means.
|
|
161
|
+
* component replacement resolver decides what it means. A doc that fails to
|
|
162
|
+
* load is reported under its file name: what it says cannot be trusted.
|
|
145
163
|
*
|
|
146
164
|
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
165
|
+
* @param {{documentedNames?: boolean}} [options] as for
|
|
166
|
+
* {@link discoverIntegrationComponents}
|
|
147
167
|
* @returns {Promise<{
|
|
148
168
|
* components: Array<{name: string, package: string, docPath: string, sourcePath: string, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>,
|
|
149
169
|
* discovered: Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>,
|
|
@@ -154,6 +174,8 @@ export function discoverValidIntegrationComponents(integration: {
|
|
|
154
174
|
name: string;
|
|
155
175
|
components?: string;
|
|
156
176
|
issuesUrl?: string;
|
|
177
|
+
}, options?: {
|
|
178
|
+
documentedNames?: boolean;
|
|
157
179
|
}): Promise<{
|
|
158
180
|
components: Array<{
|
|
159
181
|
name: string;
|
|
@@ -177,10 +199,43 @@ export function discoverValidIntegrationComponents(integration: {
|
|
|
177
199
|
message: string;
|
|
178
200
|
}>;
|
|
179
201
|
}>;
|
|
202
|
+
/**
|
|
203
|
+
* How an owner holds a component name, which the owner rule
|
|
204
|
+
* ({@link unscopedComponentOwners}) reads: `core` when Core documents it;
|
|
205
|
+
* `file` when an integration doc named after its file is recorded under it;
|
|
206
|
+
* `doc` when only a doc records it: an export name, or the file name of a doc
|
|
207
|
+
* recorded under another export.
|
|
208
|
+
* @typedef {'core' | 'file' | 'doc'} NameHold
|
|
209
|
+
*/
|
|
210
|
+
/**
|
|
211
|
+
* How a discovered integration record holds its name.
|
|
212
|
+
* @param {{name: string, docPath: string|null}} record
|
|
213
|
+
* @returns {'file' | 'doc'}
|
|
214
|
+
*/
|
|
215
|
+
export function integrationRecordHold(record: {
|
|
216
|
+
name: string;
|
|
217
|
+
docPath: string | null;
|
|
218
|
+
}): "file" | "doc";
|
|
219
|
+
/**
|
|
220
|
+
* The integration component a name finds, as discovery records it, and its
|
|
221
|
+
* hold: the same-stem doc first, then a doc that records the name.
|
|
222
|
+
*
|
|
223
|
+
* @param {{components?: string}} integration
|
|
224
|
+
* @param {string} name
|
|
225
|
+
* @returns {{docPath: string, sourcePath: string|null, hold: 'file' | 'doc'} | null}
|
|
226
|
+
*/
|
|
227
|
+
export function findIntegrationComponent(integration: {
|
|
228
|
+
components?: string;
|
|
229
|
+
}, name: string): {
|
|
230
|
+
docPath: string;
|
|
231
|
+
sourcePath: string | null;
|
|
232
|
+
hold: "file" | "doc";
|
|
233
|
+
} | null;
|
|
180
234
|
/**
|
|
181
235
|
* Find an integration component's doc file by name within a loaded
|
|
182
236
|
* integration's resolved `components` dir. Honors the same-stem convention
|
|
183
|
-
* (`{Name}.doc.{ts,mjs,js}`), preferring `.ts` → `.mjs` → `.js
|
|
237
|
+
* (`{Name}.doc.{ts,mjs,js}`), preferring `.ts` → `.mjs` → `.js`, and finds a
|
|
238
|
+
* doc that records the component under a differently named export of its file.
|
|
184
239
|
*
|
|
185
240
|
* @param {{components?: string}} integration
|
|
186
241
|
* @param {string} name bare component name (no `XDS`/`Astryx` prefix)
|
|
@@ -190,9 +245,9 @@ export function findIntegrationComponentDoc(integration: {
|
|
|
190
245
|
components?: string;
|
|
191
246
|
}, name: string): string | null;
|
|
192
247
|
/**
|
|
193
|
-
* Find an integration component's swizzleable source file
|
|
194
|
-
*
|
|
195
|
-
* when the integration ships docs without source.
|
|
248
|
+
* Find an integration component's swizzleable source file by name within a
|
|
249
|
+
* loaded integration's resolved `components` dir: the same-stem `{Name}.tsx`
|
|
250
|
+
* of its doc. Returns null when the integration ships docs without source.
|
|
196
251
|
*
|
|
197
252
|
* @param {{components?: string}} integration
|
|
198
253
|
* @param {string} name bare component name
|
|
@@ -201,6 +256,20 @@ export function findIntegrationComponentDoc(integration: {
|
|
|
201
256
|
export function findIntegrationComponentSource(integration: {
|
|
202
257
|
components?: string;
|
|
203
258
|
}, name: string): string | null;
|
|
259
|
+
/**
|
|
260
|
+
* The owner rule: an owner that holds a name only because a doc records it
|
|
261
|
+
* (`hold: 'doc'`) yields to Core and to every file-named owner; `--package`
|
|
262
|
+
* still reaches it. Surfaces resolve a replacement first, then this rule, then
|
|
263
|
+
* which docs load.
|
|
264
|
+
*
|
|
265
|
+
* @template {{hold?: string}} T
|
|
266
|
+
* @param {T[]} owners each with its {@link NameHold}; an owner without one
|
|
267
|
+
* counts as Core's or file-named
|
|
268
|
+
* @returns {T[]}
|
|
269
|
+
*/
|
|
270
|
+
export function unscopedComponentOwners<T extends {
|
|
271
|
+
hold?: string;
|
|
272
|
+
}>(owners: T[]): T[];
|
|
204
273
|
/**
|
|
205
274
|
* Build a flat list of ownership records for ALL discoverable components —
|
|
206
275
|
* core (built-in) plus every loaded integration. This is the authoritative
|
|
@@ -229,3 +298,11 @@ export function discoverOwnedComponents(coreDir: string, loadedIntegrations?: Ar
|
|
|
229
298
|
}>;
|
|
230
299
|
/** The owner package name for built-in (core) components. */
|
|
231
300
|
export const CORE_PACKAGE: import("../../authoring/index.js").ProviderId;
|
|
301
|
+
/**
|
|
302
|
+
* How an owner holds a component name, which the owner rule
|
|
303
|
+
* ({@link unscopedComponentOwners}) reads: `core` when Core documents it;
|
|
304
|
+
* `file` when an integration doc named after its file is recorded under it;
|
|
305
|
+
* `doc` when only a doc records it: an export name, or the file name of a doc
|
|
306
|
+
* recorded under another export.
|
|
307
|
+
*/
|
|
308
|
+
export type NameHold = "core" | "file" | "doc";
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Integration components are found by the names their docs document;
|
|
5
|
+
* every doc that works today reads exactly as before.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {
|
|
13
|
+
discoverIntegrationComponents,
|
|
14
|
+
discoverValidIntegrationComponents,
|
|
15
|
+
findIntegrationComponent,
|
|
16
|
+
findIntegrationComponentDoc,
|
|
17
|
+
findIntegrationComponentSource,
|
|
18
|
+
unscopedComponentOwners,
|
|
19
|
+
} from './component-discovery.mjs';
|
|
20
|
+
|
|
21
|
+
/** @type {string} the integration's components root */
|
|
22
|
+
let root;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-export-names-'));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
fs.rmSync(root, {recursive: true, force: true});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Write a component doc, and its same-stem source when given.
|
|
34
|
+
* @param {string} file path under the root, without extension
|
|
35
|
+
* @param {{name: string, source?: string, form?: 'default' | 'docs', suffix?: string, extra?: string}} options
|
|
36
|
+
* `name` is written as a JavaScript expression
|
|
37
|
+
*/
|
|
38
|
+
function writeComponent(
|
|
39
|
+
file,
|
|
40
|
+
{name, source, form = 'default', suffix = '.doc.mjs', extra = ''},
|
|
41
|
+
) {
|
|
42
|
+
const target = path.join(root, file);
|
|
43
|
+
fs.mkdirSync(path.dirname(target), {recursive: true});
|
|
44
|
+
const doc = `{\n type: 'component',\n name: ${name},\n usage: {description: 'A component.'},\n props: [],${extra}\n}`;
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
`${target}${suffix}`,
|
|
47
|
+
form === 'default'
|
|
48
|
+
? `export default ${doc};\n`
|
|
49
|
+
: `export const docs = ${doc};\n`,
|
|
50
|
+
);
|
|
51
|
+
if (source != null) fs.writeFileSync(`${target}.tsx`, source);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const integration = () => ({name: '@acme/widgets', components: root});
|
|
55
|
+
|
|
56
|
+
/** @returns {string[]} */
|
|
57
|
+
const names = () =>
|
|
58
|
+
discoverIntegrationComponents(integration())
|
|
59
|
+
.map(record => record.name)
|
|
60
|
+
.sort();
|
|
61
|
+
|
|
62
|
+
describe('integration components whose doc names another export of its file', () => {
|
|
63
|
+
it('records a doc beside a kebab-case file under the export its doc names', async () => {
|
|
64
|
+
writeComponent('artifact-viewer', {
|
|
65
|
+
name: "'AcmeArtifactViewer'",
|
|
66
|
+
source: 'export function AcmeArtifactViewer() { return null; }\n',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const [record] = discoverIntegrationComponents(integration());
|
|
70
|
+
expect(record).toMatchObject({
|
|
71
|
+
name: 'AcmeArtifactViewer',
|
|
72
|
+
package: '@acme/widgets',
|
|
73
|
+
docPath: path.join(root, 'artifact-viewer.doc.mjs'),
|
|
74
|
+
sourcePath: path.join(root, 'artifact-viewer.tsx'),
|
|
75
|
+
});
|
|
76
|
+
expect(
|
|
77
|
+
(await discoverValidIntegrationComponents(integration())).components,
|
|
78
|
+
).toEqual([expect.objectContaining({name: 'AcmeArtifactViewer'})]);
|
|
79
|
+
expect(
|
|
80
|
+
findIntegrationComponentDoc(integration(), 'AcmeArtifactViewer'),
|
|
81
|
+
).toBe(record.docPath);
|
|
82
|
+
expect(findIntegrationComponentDoc(integration(), 'artifact-viewer')).toBe(
|
|
83
|
+
record.docPath,
|
|
84
|
+
);
|
|
85
|
+
expect(
|
|
86
|
+
findIntegrationComponentSource(integration(), 'AcmeArtifactViewer'),
|
|
87
|
+
).toBe(record.sourcePath);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('leaves a name to Core or a file-named component over one a doc records', () => {
|
|
91
|
+
const core = {package: '@astryxdesign/core', hold: 'core'};
|
|
92
|
+
const fileNamed = {package: '@beta/kit', hold: 'file'};
|
|
93
|
+
const documented = {package: '@acme/widgets', hold: 'doc'};
|
|
94
|
+
expect(unscopedComponentOwners([documented, fileNamed])).toEqual([
|
|
95
|
+
fileNamed,
|
|
96
|
+
]);
|
|
97
|
+
expect(unscopedComponentOwners([core, documented])).toEqual([core]);
|
|
98
|
+
expect(unscopedComponentOwners([documented])).toEqual([documented]);
|
|
99
|
+
|
|
100
|
+
// Badge.doc.mjs records AcmeBadge, so the doc holds both names only
|
|
101
|
+
// because it documents them; Panel.doc.mjs names an export another file
|
|
102
|
+
// has, so it keeps its file name.
|
|
103
|
+
writeComponent('Badge', {
|
|
104
|
+
name: "'AcmeBadge'",
|
|
105
|
+
source: 'export function AcmeBadge() { return null; }\n',
|
|
106
|
+
});
|
|
107
|
+
writeComponent('Panel', {
|
|
108
|
+
name: "'AcmeChip'",
|
|
109
|
+
source: 'export function AcmeChip() { return null; }\n',
|
|
110
|
+
});
|
|
111
|
+
writeComponent('AcmeChip', {
|
|
112
|
+
name: "'AcmeChip'",
|
|
113
|
+
source: 'export function AcmeChip() { return null; }\n',
|
|
114
|
+
});
|
|
115
|
+
const hold = (/** @type {string} */ name) =>
|
|
116
|
+
findIntegrationComponent(integration(), name)?.hold;
|
|
117
|
+
expect(hold('AcmeBadge')).toBe('doc');
|
|
118
|
+
expect(hold('Badge')).toBe('doc');
|
|
119
|
+
expect(hold('Panel')).toBe('file');
|
|
120
|
+
expect(hold('AcmeChip')).toBe('file');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('takes no name a hidden doc or a replacement in the package claims', () => {
|
|
124
|
+
// A hidden doc is still the file named AcmeCarousel: its name stays its own.
|
|
125
|
+
writeComponent('AcmeCarousel', {
|
|
126
|
+
name: "'AcmeCarousel'",
|
|
127
|
+
extra: '\n hidden: true,',
|
|
128
|
+
source: 'export function AcmeCarousel() { return null; }\n',
|
|
129
|
+
});
|
|
130
|
+
writeComponent('gallery/Slides', {
|
|
131
|
+
name: "'AcmeCarousel'",
|
|
132
|
+
source: 'export function AcmeCarousel() { return null; }\n',
|
|
133
|
+
});
|
|
134
|
+
// A doc in the package replaces Carousel, so that name answers with it.
|
|
135
|
+
writeComponent('Pager', {
|
|
136
|
+
name: "'AcmePager'",
|
|
137
|
+
extra: "\n replaces: 'Carousel',",
|
|
138
|
+
source: 'export function AcmePager() { return null; }\n',
|
|
139
|
+
});
|
|
140
|
+
writeComponent('Kit', {
|
|
141
|
+
name: "'Carousel'",
|
|
142
|
+
source: 'export function Carousel() { return null; }\n',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(names()).toEqual(['AcmePager', 'Kit', 'Slides']);
|
|
146
|
+
expect(findIntegrationComponentDoc(integration(), 'AcmeCarousel')).toBe(
|
|
147
|
+
path.join(root, 'AcmeCarousel.doc.mjs'),
|
|
148
|
+
);
|
|
149
|
+
expect(findIntegrationComponentDoc(integration(), 'Carousel')).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('settles a name two docs document by path, the same in every locale', () => {
|
|
153
|
+
// By UTF-16 code unit `B/` sorts before `a/`; a locale's collation puts
|
|
154
|
+
// `a/` first.
|
|
155
|
+
for (const [file, stem] of [
|
|
156
|
+
['a/Alpha', 'Alpha'],
|
|
157
|
+
['B/Beta', 'Beta'],
|
|
158
|
+
]) {
|
|
159
|
+
writeComponent(file, {
|
|
160
|
+
name: "'Twin'",
|
|
161
|
+
source: `export function Twin() { return null; }\n// ${stem}\n`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
expect(names()).toEqual(['Alpha', 'Twin']);
|
|
166
|
+
expect(findIntegrationComponentDoc(integration(), 'Twin')).toBe(
|
|
167
|
+
path.join(root, 'B', 'Beta.doc.mjs'),
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('reads a name a spread can override, and reports an unreadable doc under its file name', async () => {
|
|
172
|
+
fs.writeFileSync(
|
|
173
|
+
path.join(root, 'Based.doc.mjs'),
|
|
174
|
+
"const base = {\n name: 'Based',\n type: 'component',\n usage: {description: 'x'},\n props: [],\n};\nexport default {...base, name: 'AcmeBased'};\n",
|
|
175
|
+
);
|
|
176
|
+
fs.writeFileSync(
|
|
177
|
+
path.join(root, 'Based.tsx'),
|
|
178
|
+
'export function AcmeBased() { return null; }\n',
|
|
179
|
+
);
|
|
180
|
+
writeComponent('Throws', {
|
|
181
|
+
name: "'AcmeThrows'",
|
|
182
|
+
source: 'export function AcmeThrows() { return null; }\n',
|
|
183
|
+
});
|
|
184
|
+
const throws = path.join(root, 'Throws.doc.mjs');
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
throws,
|
|
187
|
+
`throw new Error('boom');\n${fs.readFileSync(throws, 'utf-8')}`,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(names()).toEqual(['AcmeBased', 'AcmeThrows']);
|
|
191
|
+
const {errors} = await discoverValidIntegrationComponents(integration());
|
|
192
|
+
expect(errors).toEqual([
|
|
193
|
+
{
|
|
194
|
+
name: 'Throws',
|
|
195
|
+
message: 'Component "Throws" has invalid metadata: boom',
|
|
196
|
+
},
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('reads a legacy export, a .doc.ts doc and a re-exported component the same way', () => {
|
|
201
|
+
writeComponent('Slides', {
|
|
202
|
+
name: "'AcmeCarousel'",
|
|
203
|
+
form: 'docs',
|
|
204
|
+
source: "export {AcmeCarousel} from './carousel/impl';\n",
|
|
205
|
+
});
|
|
206
|
+
fs.mkdirSync(path.join(root, 'carousel'));
|
|
207
|
+
fs.writeFileSync(
|
|
208
|
+
path.join(root, 'carousel', 'impl.tsx'),
|
|
209
|
+
'export function AcmeCarousel() { return null; }\n',
|
|
210
|
+
);
|
|
211
|
+
writeComponent('Pager', {
|
|
212
|
+
name: "'AcmePager'",
|
|
213
|
+
suffix: '.doc.ts',
|
|
214
|
+
source: 'export const AcmePager = () => null;\n',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(names()).toEqual(['AcmeCarousel', 'AcmePager']);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('keeps the file name for every doc that reads as before', async () => {
|
|
221
|
+
// Named after its file.
|
|
222
|
+
writeComponent('AcmeBadge', {
|
|
223
|
+
name: "'AcmeBadge'",
|
|
224
|
+
source: 'export function AcmeBadge() { return null; }\n',
|
|
225
|
+
});
|
|
226
|
+
// The file exports its own name, so that name stays.
|
|
227
|
+
writeComponent('Chip', {
|
|
228
|
+
name: "'AcmeChip'",
|
|
229
|
+
source:
|
|
230
|
+
'export function Chip() { return null; }\nexport function AcmeChip() { return null; }\n',
|
|
231
|
+
});
|
|
232
|
+
// The doc names an export the file does not have.
|
|
233
|
+
writeComponent('Menu', {
|
|
234
|
+
name: "'AcmeMenu'",
|
|
235
|
+
source: 'export function Dropdown() { return null; }\n',
|
|
236
|
+
});
|
|
237
|
+
// A type is not a runtime export.
|
|
238
|
+
writeComponent('Shape', {
|
|
239
|
+
name: "'AcmeShape'",
|
|
240
|
+
source: 'export type AcmeShape = {id: string};\n',
|
|
241
|
+
});
|
|
242
|
+
// A name the doc computes is not read.
|
|
243
|
+
writeComponent('Tile', {
|
|
244
|
+
name: "`${'Acme'}Tile`",
|
|
245
|
+
source: 'export function AcmeTile() { return null; }\n',
|
|
246
|
+
});
|
|
247
|
+
// Another file already has the name.
|
|
248
|
+
writeComponent('Panel', {
|
|
249
|
+
name: "'AcmeBadge'",
|
|
250
|
+
source: 'export function AcmeBadge() { return null; }\n',
|
|
251
|
+
});
|
|
252
|
+
// No source beside it.
|
|
253
|
+
writeComponent('Orphan', {name: "'AcmeOrphan'"});
|
|
254
|
+
|
|
255
|
+
expect(names()).toEqual([
|
|
256
|
+
'AcmeBadge',
|
|
257
|
+
'Chip',
|
|
258
|
+
'Menu',
|
|
259
|
+
'Orphan',
|
|
260
|
+
'Panel',
|
|
261
|
+
'Shape',
|
|
262
|
+
'Tile',
|
|
263
|
+
]);
|
|
264
|
+
for (const name of [
|
|
265
|
+
'AcmeChip',
|
|
266
|
+
'AcmeMenu',
|
|
267
|
+
'AcmeShape',
|
|
268
|
+
'AcmeTile',
|
|
269
|
+
'AcmeOrphan',
|
|
270
|
+
]) {
|
|
271
|
+
expect(findIntegrationComponentDoc(integration(), name)).toBeNull();
|
|
272
|
+
}
|
|
273
|
+
expect(
|
|
274
|
+
(await discoverValidIntegrationComponents(integration())).errors,
|
|
275
|
+
).toEqual([
|
|
276
|
+
{
|
|
277
|
+
name: 'Orphan',
|
|
278
|
+
message:
|
|
279
|
+
'Component "Orphan" is missing its same-stem source file Orphan.tsx.',
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('still leaves a hidden doc out', () => {
|
|
285
|
+
writeComponent('Slides', {
|
|
286
|
+
name: "'AcmeCarousel'",
|
|
287
|
+
extra: '\n hidden: true,',
|
|
288
|
+
source: 'export function AcmeCarousel() { return null; }\n',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(names()).toEqual([]);
|
|
292
|
+
});
|
|
293
|
+
});
|