@astryxdesign/cli 0.3.0-canary.fbc60c5 → 0.3.0-canary.fcbc342
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 +8 -8
- package/api/blog/blog.doc.mjs +2 -2
- package/api/build/build.doc.mjs +1 -1
- package/api/component/component.doc.mjs +1 -1
- package/api/discover/discover.doc.mjs +2 -2
- package/api/docs/docs.doc.mjs +2 -2
- package/api/doctor/doctor.doc.mjs +3 -3
- package/api/hook/hook.doc.mjs +1 -1
- package/api/init/init.doc.mjs +1 -1
- package/api/integration/summarizeIssues.doc.mjs +1 -1
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +2 -2
- package/api/json/isError.doc.mjs +1 -1
- package/api/json/parseResponse.doc.mjs +2 -2
- package/api/layout/layoutCheck.doc.mjs +1 -1
- package/api/layout/layoutExpand.doc.mjs +1 -1
- package/api/layout/layoutGrammar.doc.mjs +1 -1
- package/api/swizzle/swizzle.doc.mjs +1 -1
- package/api/template/template.test.mjs +38 -13
- package/api/theme/listThemes.doc.mjs +2 -2
- package/api/theme/themeAdd.doc.mjs +1 -1
- package/api/theme/themeBuild.doc.mjs +3 -3
- package/api/theme/themeList.doc.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +1 -1
- package/assets/codemods/transforms/next/__tests__/next-codemods.test.mjs +92 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/rename-dropdown-menu-radio-dot-target.mjs +152 -0
- package/assets/codemods/transforms/v0.3.0/__tests__/migrate-grid-minchildwidth-to-columns.test.mjs +82 -0
- package/assets/codemods/transforms/v0.3.0/__tests__/migrate-table-rowexpansion-to-tree.test.mjs +201 -0
- package/assets/codemods/transforms/v0.3.0/index.mjs +8 -0
- package/assets/codemods/transforms/v0.3.0/migrate-grid-minchildwidth-to-columns.mjs +38 -2
- package/assets/codemods/transforms/v0.3.0/migrate-table-rowexpansion-to-tree.mjs +214 -0
- package/assets/docs/browser-support.doc.mjs +6 -6
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/icons.doc.mjs +3 -3
- package/assets/docs/illustrations.doc.mjs +2 -2
- package/assets/docs/internationalization.doc.mjs +1 -1
- package/assets/docs/layout.doc.dense.mjs +1 -1
- package/assets/docs/migration.doc.mjs +1 -1
- package/assets/docs/principles.doc.mjs +1 -1
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/styling.doc.mjs +4 -4
- package/assets/docs/theme.doc.mjs +3 -3
- package/assets/docs/typography.doc.mjs +2 -2
- package/assets/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +1 -1
- package/assets/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +1 -1
- package/assets/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +3 -3
- package/assets/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +1 -1
- package/assets/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +1 -1
- package/assets/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +6 -6
- package/assets/templates/blocks/components/Avatar/AvatarGroup.tsx +5 -5
- package/assets/templates/blocks/components/Avatar/AvatarInteractive.tsx +2 -2
- package/assets/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
- package/assets/templates/blocks/components/Avatar/AvatarTooltip.tsx +4 -4
- package/assets/templates/blocks/components/Avatar/AvatarUserCard.tsx +3 -3
- package/assets/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
- package/assets/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
- package/assets/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerAttachments.tsx +5 -5
- package/assets/templates/blocks/components/Lightbox/LightboxGallery.tsx +4 -4
- package/assets/templates/blocks/components/Lightbox/LightboxShowcase.tsx +1 -1
- package/assets/templates/blocks/components/Lightbox/LightboxVideo.tsx +1 -1
- package/assets/templates/blocks/components/Lightbox/LightboxZoom.tsx +2 -2
- package/assets/templates/blocks/components/MediaTheme/MediaThemeImageOverlay.tsx +1 -1
- package/assets/templates/blocks/components/MediaTheme/MediaThemeLightScrim.tsx +1 -1
- package/assets/templates/blocks/components/MediaTheme/MediaThemeShowcase.tsx +1 -1
- package/assets/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +1 -1
- package/assets/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +1 -1
- package/assets/templates/blocks/components/Overlay/OverlayShowcase.tsx +1 -1
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +24 -0
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +68 -0
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +92 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +41 -0
- package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +25 -0
- package/assets/templates/blocks/components/Stepper/StepperStatus.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +50 -0
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +40 -0
- package/assets/templates/blocks/components/Table/TableRowExpansionTable.tsx +61 -58
- package/assets/templates/blocks/components/TextArea/TextAreaStates.tsx +1 -1
- package/assets/templates/blocks/components/TopNav/TopNavMegaMenu.tsx +1 -1
- package/assets/templates/pages/centered-hero/page.tsx +1 -1
- package/assets/templates/pages/classic-gallery/page.tsx +10 -10
- package/assets/templates/pages/detail-page/page.tsx +5 -5
- package/assets/templates/pages/form-two-column/page.tsx +1 -1
- package/assets/templates/pages/gallery-hero/page.tsx +3 -3
- package/assets/templates/pages/library/page.tsx +30 -30
- package/assets/templates/pages/login/page.tsx +1 -2
- package/assets/templates/pages/login-card/page.tsx +1 -2
- package/assets/templates/pages/login-split/page.tsx +5 -6
- package/assets/templates/pages/login-sso/page.tsx +2 -3
- package/assets/templates/pages/mixed-gallery/page.tsx +5 -5
- package/assets/templates/pages/payment-form/page.tsx +3 -3
- package/assets/templates/pages/product-detail/page.tsx +7 -7
- package/assets/templates/pages/product-gallery/page.tsx +6 -6
- package/assets/templates/pages/shell-top-nav/page.tsx +2 -2
- package/assets/templates/pages/side-gallery/page.tsx +9 -9
- package/assets/templates/pages/table-page-chart/page.tsx +6 -6
- package/assets/templates/pages/theme-showcase/page.tsx +6 -6
- package/assets/templates/themes/neutral/neutralTheme.ts +4 -1
- package/authoring/codemod/codemod.doc.mjs +2 -2
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/doctypes/base/type.ts +21 -0
- package/authoring/doctypes/command/command.doc.mjs +2 -2
- package/authoring/doctypes/component/component.doc.mjs +7 -7
- package/authoring/doctypes/enum/enum.doc.mjs +2 -2
- package/authoring/doctypes/function/function.doc.mjs +3 -3
- package/authoring/doctypes/hook/hook.doc.mjs +3 -3
- package/authoring/doctypes/reference/reference.doc.mjs +3 -3
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/clients/cli/commands/build-theme.registry.test.mjs +14 -1
- package/clients/cli/commands/build.doc.mjs +1 -1
- package/clients/cli/commands/component.doc.mjs +1 -1
- package/clients/cli/commands/doctor.doc.mjs +2 -2
- package/clients/cli/commands/layout-expand.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +3 -3
- package/clients/cli/commands/search.doc.mjs +1 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/clients/cli/commands/theme-list.doc.mjs +1 -1
- package/clients/cli/commands/theme.doc.mjs +2 -2
- package/clients/cli/commands/validate-integration.doc.mjs +2 -2
- package/foundation/agent-docs/agent-docs.mjs +3 -4
- package/foundation/agent-docs/agent-docs.test.mjs +8 -0
- package/foundation/discovery/template-adapter.d.mts +6 -3
- package/foundation/discovery/template-adapter.mjs +33 -13
- package/foundation/response/response-types.doc.mjs +7 -7
- package/foundation/response/response.doc.mjs +5 -5
- package/package.json +9 -9
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: rename the removed `dropdown-menu-radio-dot` theme target
|
|
5
|
+
*
|
|
6
|
+
* The menu radio's dot is no longer drawn by DropdownMenuRadioItem. That row
|
|
7
|
+
* now renders the shared radio indicator, so its dot is the indicator's dot and
|
|
8
|
+
* carries `radio-indicator-dot` (plus the legacy `radio-dot`) instead of the
|
|
9
|
+
* menu-specific `dropdown-menu-radio-dot`, which is gone.
|
|
10
|
+
*
|
|
11
|
+
* Runtime themes are not validated: a theme keyed on the old target keeps
|
|
12
|
+
* compiling and simply stops matching, with no error anywhere. That silence is
|
|
13
|
+
* why this rename needs a codemod rather than a changelog line.
|
|
14
|
+
*
|
|
15
|
+
* The rename is NOT scope-preserving, and that cannot be fixed here — there is
|
|
16
|
+
* no menu-only dot element left to address. `radio-indicator-dot` reaches every
|
|
17
|
+
* radio dot in the app, including RadioList's. So every rewritten site also
|
|
18
|
+
* gets a TODO comment (api.report is a stub; comments are the only warning
|
|
19
|
+
* channel) telling the author to check whether the rule was meant to be
|
|
20
|
+
* menu-only, and pointing at the containing-target route if it was.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const meta = {
|
|
24
|
+
title: 'Rename the removed dropdown-menu-radio-dot theme target',
|
|
25
|
+
description:
|
|
26
|
+
'Renames the `dropdown-menu-radio-dot` theme target (and the ' +
|
|
27
|
+
'`astryx-dropdown-menu-radio-dot` class it rendered) to ' +
|
|
28
|
+
'`radio-indicator-dot` / `astryx-radio-indicator-dot`. Menu radios draw ' +
|
|
29
|
+
'the shared radio indicator now, so the menu-specific dot target no ' +
|
|
30
|
+
'longer exists. The new target is app-wide rather than menu-only, so each ' +
|
|
31
|
+
'rewritten site gets a TODO comment to confirm that widening is intended.',
|
|
32
|
+
pr: '#4712',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const OLD_TARGET = 'dropdown-menu-radio-dot';
|
|
36
|
+
const NEW_TARGET = 'radio-indicator-dot';
|
|
37
|
+
const OLD_CLASS = `astryx-${OLD_TARGET}`;
|
|
38
|
+
const NEW_CLASS = `astryx-${NEW_TARGET}`;
|
|
39
|
+
|
|
40
|
+
const TODO_COMMENT =
|
|
41
|
+
' TODO(astryx upgrade): `dropdown-menu-radio-dot` became `radio-indicator-dot`,' +
|
|
42
|
+
' which styles EVERY radio dot, not just the ones in a menu. If this rule was' +
|
|
43
|
+
' meant to be menu-only, scope it under the containing `dropdown-menu-radio`' +
|
|
44
|
+
' target instead of this one. ';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Rewrite one string value, or return null when it holds nothing to rename.
|
|
48
|
+
*
|
|
49
|
+
* Handles the target name on its own (a theme's `components` key) and the
|
|
50
|
+
* rendered class inside a larger string (a selector such as
|
|
51
|
+
* `.astryx-dropdown-menu-radio-dot`, or a className list).
|
|
52
|
+
*
|
|
53
|
+
* @param {string} value
|
|
54
|
+
* @returns {string | null}
|
|
55
|
+
*/
|
|
56
|
+
function renameIn(value) {
|
|
57
|
+
if (value === OLD_TARGET) {
|
|
58
|
+
return NEW_TARGET;
|
|
59
|
+
}
|
|
60
|
+
if (value.includes(OLD_CLASS)) {
|
|
61
|
+
return value.split(OLD_CLASS).join(NEW_CLASS);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {import('../../../../authoring/codemod/type').AstryxCodemodFile} file
|
|
68
|
+
* @param {import('../../../../authoring/codemod/type').CodemodTransformApi} api
|
|
69
|
+
* @returns {string | null | undefined}
|
|
70
|
+
*/
|
|
71
|
+
export default function transformer(file, api) {
|
|
72
|
+
// Cheap bail-out: most files in a consumer repo mention neither name.
|
|
73
|
+
if (!file.source.includes(OLD_TARGET)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const j = api.jscodeshift;
|
|
78
|
+
const root = j(file.source);
|
|
79
|
+
let hasChanges = false;
|
|
80
|
+
|
|
81
|
+
/** Attach the widening warning once to the nearest statement-ish node. */
|
|
82
|
+
function attachTodo(/** @type {any} */ path) {
|
|
83
|
+
// The property (or its statement) reads better than the bare literal, and
|
|
84
|
+
// matches where a human would look for the note.
|
|
85
|
+
const host =
|
|
86
|
+
path.parent?.node?.type === 'ObjectProperty' ||
|
|
87
|
+
path.parent?.node?.type === 'Property'
|
|
88
|
+
? path.parent.node
|
|
89
|
+
: path.node;
|
|
90
|
+
if (!host.comments) {
|
|
91
|
+
host.comments = [];
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
host.comments.some((/** @type {any} */ c) => c.value === TODO_COMMENT)
|
|
95
|
+
) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
host.comments.push(j.commentBlock(TODO_COMMENT, true, false));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
root
|
|
102
|
+
.find(j.StringLiteral)
|
|
103
|
+
.forEach((/** @type {any} */ path) => {
|
|
104
|
+
const renamed = renameIn(path.node.value);
|
|
105
|
+
if (renamed == null) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
path.node.value = renamed;
|
|
109
|
+
attachTodo(path);
|
|
110
|
+
hasChanges = true;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Older parsers surface string literals as `Literal`.
|
|
114
|
+
root.find(j.Literal).forEach((/** @type {any} */ path) => {
|
|
115
|
+
if (typeof path.node.value !== 'string') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const renamed = renameIn(path.node.value);
|
|
119
|
+
if (renamed == null) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
path.node.value = renamed;
|
|
123
|
+
if (typeof path.node.raw === 'string') {
|
|
124
|
+
path.node.raw = path.node.raw
|
|
125
|
+
.split(OLD_TARGET)
|
|
126
|
+
.join(NEW_TARGET);
|
|
127
|
+
}
|
|
128
|
+
attachTodo(path);
|
|
129
|
+
hasChanges = true;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Template literals carry the class in CSS strings: `.${OLD_CLASS} > span`.
|
|
133
|
+
root.find(j.TemplateElement).forEach((/** @type {any} */ path) => {
|
|
134
|
+
const cooked = path.node.value?.cooked;
|
|
135
|
+
if (typeof cooked !== 'string') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const renamed = renameIn(cooked);
|
|
139
|
+
if (renamed == null) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
path.node.value.cooked = renamed;
|
|
143
|
+
path.node.value.raw = path.node.value.raw
|
|
144
|
+
.split(OLD_CLASS)
|
|
145
|
+
.join(NEW_CLASS)
|
|
146
|
+
.split(OLD_TARGET)
|
|
147
|
+
.join(NEW_TARGET);
|
|
148
|
+
hasChanges = true;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return hasChanges ? root.toSource({quote: 'single'}) : undefined;
|
|
152
|
+
}
|
package/assets/codemods/transforms/v0.3.0/__tests__/migrate-grid-minchildwidth-to-columns.test.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
async function applyTransform(source) {
|
|
6
|
+
const {default: transform} =
|
|
7
|
+
await import('../migrate-grid-minchildwidth-to-columns.mjs');
|
|
8
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
9
|
+
const j = jscodeshift.withParser('tsx');
|
|
10
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
11
|
+
const file = {source, path: 'test.tsx'};
|
|
12
|
+
const result = transform(file, api);
|
|
13
|
+
return result ?? source;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Collapse whitespace (and space padding inside braces) so formatting of
|
|
17
|
+
* pretty-printed multiline objects does not matter for assertions. */
|
|
18
|
+
function normalize(str) {
|
|
19
|
+
return str
|
|
20
|
+
.replace(/\s+/g, ' ')
|
|
21
|
+
.replace(/\{\s+/g, '{')
|
|
22
|
+
.replace(/\s+\}/g, '}')
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('migrate-grid-minchildwidth-to-columns', () => {
|
|
27
|
+
it('migrates minChildWidth only to columns={{minWidth, repeat: "fit"}}', async () => {
|
|
28
|
+
const input = `import {Grid} from '@astryxdesign/core';
|
|
29
|
+
const t = <Grid minChildWidth={280}><Item /></Grid>;`;
|
|
30
|
+
const output = await applyTransform(input);
|
|
31
|
+
expect(output).not.toContain('minChildWidth');
|
|
32
|
+
expect(normalize(output)).toContain(
|
|
33
|
+
normalize("columns={{minWidth: 280, repeat: 'fit'}}"),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('migrates columns={n} + minChildWidth to columns={{minWidth, max, repeat: "fit"}}', async () => {
|
|
38
|
+
const input = `import {Grid} from '@astryxdesign/core';
|
|
39
|
+
const t = <Grid columns={3} minChildWidth={280}><Item /></Grid>;`;
|
|
40
|
+
const output = await applyTransform(input);
|
|
41
|
+
expect(output).not.toContain('minChildWidth');
|
|
42
|
+
expect(normalize(output)).toContain(
|
|
43
|
+
normalize("columns={{minWidth: 280, max: 3, repeat: 'fit'}}"),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('preserves other attributes (gap) when migrating the combo', async () => {
|
|
48
|
+
const input = `import {Grid} from '@astryxdesign/core';
|
|
49
|
+
const t = <Grid columns={5} gap={5} minChildWidth={140}><Item /></Grid>;`;
|
|
50
|
+
const output = await applyTransform(input);
|
|
51
|
+
expect(output).not.toContain('minChildWidth');
|
|
52
|
+
expect(normalize(output)).toContain(
|
|
53
|
+
normalize("columns={{minWidth: 140, max: 5, repeat: 'fit'}}"),
|
|
54
|
+
);
|
|
55
|
+
expect(output).toContain('gap={5}');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('bails when columns is already an object (ambiguous)', async () => {
|
|
59
|
+
const input = `import {Grid} from '@astryxdesign/core';
|
|
60
|
+
const t = <Grid columns={{minWidth: 100}} minChildWidth={280}><Item /></Grid>;`;
|
|
61
|
+
const output = await applyTransform(input);
|
|
62
|
+
// Unchanged: object columns is ambiguous, so it is left as-is.
|
|
63
|
+
expect(output).toBe(input);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('bails when columns is a non-numeric/dynamic expression', async () => {
|
|
67
|
+
const input = `import {Grid} from '@astryxdesign/core';
|
|
68
|
+
const t = <Grid columns={cols} minChildWidth={280}><Item /></Grid>;`;
|
|
69
|
+
const output = await applyTransform(input);
|
|
70
|
+
expect(output).toBe(input);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('is alias-aware and supports the subpath import source', async () => {
|
|
74
|
+
const input = `import {Grid as G} from '@astryxdesign/core/Grid';
|
|
75
|
+
const t = <G columns={4} minChildWidth={200}><Item /></G>;`;
|
|
76
|
+
const output = await applyTransform(input);
|
|
77
|
+
expect(output).not.toContain('minChildWidth');
|
|
78
|
+
expect(normalize(output)).toContain(
|
|
79
|
+
normalize("columns={{minWidth: 200, max: 4, repeat: 'fit'}}"),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
package/assets/codemods/transforms/v0.3.0/__tests__/migrate-table-rowexpansion-to-tree.test.mjs
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
async function applyTransform(source) {
|
|
6
|
+
const {default: transform} = await import(
|
|
7
|
+
'../migrate-table-rowexpansion-to-tree.mjs'
|
|
8
|
+
);
|
|
9
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
10
|
+
const j = jscodeshift.withParser('tsx');
|
|
11
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
12
|
+
const file = {source, path: 'test.tsx'};
|
|
13
|
+
const result = transform(file, api);
|
|
14
|
+
return result ?? source;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalize(str) {
|
|
18
|
+
return str
|
|
19
|
+
.replace(/\s+/g, ' ')
|
|
20
|
+
.replace(/\{\s+/g, '{')
|
|
21
|
+
.replace(/\s+\}/g, '}')
|
|
22
|
+
.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('migrate-table-rowexpansion-to-tree', () => {
|
|
26
|
+
it('rewrites the useTableRowExpansionState import to useTableTreeState', async () => {
|
|
27
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
28
|
+
function C() {
|
|
29
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
30
|
+
baseData: tree,
|
|
31
|
+
getChildren: item => item.children ?? [],
|
|
32
|
+
getRowKey: item => item.id,
|
|
33
|
+
expandedKeys,
|
|
34
|
+
setExpandedKeys,
|
|
35
|
+
});
|
|
36
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
37
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
38
|
+
}`;
|
|
39
|
+
const output = await applyTransform(input);
|
|
40
|
+
expect(output).not.toContain('useTableRowExpansionState');
|
|
41
|
+
expect(output).toContain('useTableTreeState');
|
|
42
|
+
expect(output).toContain('useTableTreeData');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('maps baseData to data and getChildren to childrenKey', async () => {
|
|
46
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
47
|
+
function C() {
|
|
48
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
49
|
+
baseData: tree,
|
|
50
|
+
getChildren: item => item.children ?? [],
|
|
51
|
+
getRowKey: item => item.id,
|
|
52
|
+
expandedKeys,
|
|
53
|
+
setExpandedKeys,
|
|
54
|
+
});
|
|
55
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
56
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
57
|
+
}`;
|
|
58
|
+
const output = await applyTransform(input);
|
|
59
|
+
const n = normalize(output);
|
|
60
|
+
expect(n).toContain('data: tree');
|
|
61
|
+
expect(n).toContain("childrenKey: 'children'");
|
|
62
|
+
expect(output).not.toContain('baseData');
|
|
63
|
+
expect(output).not.toContain('getChildren');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('maps getRowKey to idKey preserving the accessor function', async () => {
|
|
67
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
68
|
+
function C() {
|
|
69
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
70
|
+
baseData: tree,
|
|
71
|
+
getChildren: item => item.children ?? [],
|
|
72
|
+
getRowKey: item => item.id,
|
|
73
|
+
expandedKeys,
|
|
74
|
+
setExpandedKeys,
|
|
75
|
+
});
|
|
76
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
77
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
78
|
+
}`;
|
|
79
|
+
const output = await applyTransform(input);
|
|
80
|
+
const n = normalize(output);
|
|
81
|
+
expect(n).toContain('idKey: item => item.id');
|
|
82
|
+
expect(output).not.toContain('getRowKey');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('swaps the useTableRowExpansion plugin call for useTableTreeData', async () => {
|
|
86
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
87
|
+
function C() {
|
|
88
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
89
|
+
baseData: tree,
|
|
90
|
+
getChildren: item => item.children ?? [],
|
|
91
|
+
getRowKey: item => item.id,
|
|
92
|
+
expandedKeys,
|
|
93
|
+
setExpandedKeys,
|
|
94
|
+
});
|
|
95
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
96
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
97
|
+
}`;
|
|
98
|
+
const output = await applyTransform(input);
|
|
99
|
+
// The state hook now returns treeConfig; the plugin consumes it.
|
|
100
|
+
expect(output).toContain('treeConfig');
|
|
101
|
+
expect(output).toContain('useTableTreeData(treeConfig)');
|
|
102
|
+
expect(output).not.toMatch(/useTableRowExpansion\(/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('maps getIsItemExpandable to isItemExpandable', async () => {
|
|
106
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
107
|
+
function C() {
|
|
108
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
109
|
+
baseData: tree,
|
|
110
|
+
getChildren: item => item.children ?? [],
|
|
111
|
+
getRowKey: item => item.id,
|
|
112
|
+
getIsItemExpandable: item => item.type === 'folder',
|
|
113
|
+
expandedKeys,
|
|
114
|
+
setExpandedKeys,
|
|
115
|
+
});
|
|
116
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
117
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
118
|
+
}`;
|
|
119
|
+
const output = await applyTransform(input);
|
|
120
|
+
expect(output).toContain('isItemExpandable');
|
|
121
|
+
expect(output).not.toContain('getIsItemExpandable');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('leaves the new detail-panel usage (renderExpanded) untouched', async () => {
|
|
125
|
+
const input = `import {Table, useTableRowExpansion} from '@astryxdesign/core';
|
|
126
|
+
function C() {
|
|
127
|
+
const expansion = useTableRowExpansion({
|
|
128
|
+
expandedKeys,
|
|
129
|
+
onToggle,
|
|
130
|
+
getRowKey: item => item.id,
|
|
131
|
+
renderExpanded: item => <Details item={item} />,
|
|
132
|
+
});
|
|
133
|
+
return <Table data={rows} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
134
|
+
}`;
|
|
135
|
+
const output = await applyTransform(input);
|
|
136
|
+
// No useTableRowExpansionState import here, and renderExpanded present:
|
|
137
|
+
// this is the new detail-panel API. The codemod must not touch it.
|
|
138
|
+
expect(output).toBe(input);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('does not touch files that never imported the row-expansion hooks', async () => {
|
|
142
|
+
const input = `import {Table, useTableSelection} from '@astryxdesign/core';
|
|
143
|
+
const t = <Table data={rows} columns={cols} idKey="id" />;`;
|
|
144
|
+
const output = await applyTransform(input);
|
|
145
|
+
expect(output).toBe(input);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('supports the @xds/core import source alias', async () => {
|
|
149
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@xds/core';
|
|
150
|
+
function C() {
|
|
151
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
152
|
+
baseData: tree,
|
|
153
|
+
getChildren: item => item.children ?? [],
|
|
154
|
+
getRowKey: item => item.id,
|
|
155
|
+
expandedKeys,
|
|
156
|
+
setExpandedKeys,
|
|
157
|
+
});
|
|
158
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
159
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
160
|
+
}`;
|
|
161
|
+
const output = await applyTransform(input);
|
|
162
|
+
expect(output).toContain('useTableTreeState');
|
|
163
|
+
expect(output).not.toContain('useTableRowExpansionState');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('emits treeConfig as a shorthand (not treeConfig: treeConfig)', async () => {
|
|
167
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
168
|
+
function C() {
|
|
169
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
170
|
+
baseData: tree,
|
|
171
|
+
getChildren: item => item.children ?? [],
|
|
172
|
+
getRowKey: item => item.id,
|
|
173
|
+
expandedKeys,
|
|
174
|
+
setExpandedKeys,
|
|
175
|
+
});
|
|
176
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
177
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
178
|
+
}`;
|
|
179
|
+
const output = await applyTransform(input);
|
|
180
|
+
expect(output).not.toContain('treeConfig: treeConfig');
|
|
181
|
+
expect(normalize(output)).toContain('visibleData: data');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('leaves a migration guidance comment about expansion state', async () => {
|
|
185
|
+
const input = `import {Table, useTableRowExpansion, useTableRowExpansionState} from '@astryxdesign/core';
|
|
186
|
+
function C() {
|
|
187
|
+
const {data, expansionConfig} = useTableRowExpansionState({
|
|
188
|
+
baseData: tree,
|
|
189
|
+
getChildren: item => item.children ?? [],
|
|
190
|
+
getRowKey: item => item.id,
|
|
191
|
+
expandedKeys,
|
|
192
|
+
setExpandedKeys,
|
|
193
|
+
});
|
|
194
|
+
const expansion = useTableRowExpansion(expansionConfig);
|
|
195
|
+
return <Table data={data} columns={cols} idKey="id" plugins={{expansion}} />;
|
|
196
|
+
}`;
|
|
197
|
+
const output = await applyTransform(input);
|
|
198
|
+
expect(output).toContain('astryx-migration');
|
|
199
|
+
expect(output).toContain('defaultExpandedIds');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -40,6 +40,9 @@ import migrateLabCodeBlockImports, {
|
|
|
40
40
|
import removeThemeTransitionTokenImports, {
|
|
41
41
|
meta as removeThemeTransitionTokenImportsMeta,
|
|
42
42
|
} from './remove-theme-transition-token-imports.mjs';
|
|
43
|
+
import migrateTableRowExpansionToTree, {
|
|
44
|
+
meta as migrateTableRowExpansionToTreeMeta,
|
|
45
|
+
} from './migrate-table-rowexpansion-to-tree.mjs';
|
|
43
46
|
|
|
44
47
|
export default [
|
|
45
48
|
{
|
|
@@ -87,4 +90,9 @@ export default [
|
|
|
87
90
|
transform: removeThemeTransitionTokenImports,
|
|
88
91
|
meta: removeThemeTransitionTokenImportsMeta,
|
|
89
92
|
},
|
|
93
|
+
{
|
|
94
|
+
name: 'migrate-table-rowexpansion-to-tree',
|
|
95
|
+
transform: migrateTableRowExpansionToTree,
|
|
96
|
+
meta: migrateTableRowExpansionToTreeMeta,
|
|
97
|
+
},
|
|
90
98
|
];
|
|
@@ -49,11 +49,10 @@ export default function transformer(file, api) {
|
|
|
49
49
|
a.type === 'JSXAttribute' && a.name?.name === 'minChildWidth',
|
|
50
50
|
);
|
|
51
51
|
if (!minAttr) return;
|
|
52
|
-
const
|
|
52
|
+
const columnsAttr = attrs.find(
|
|
53
53
|
(/** @type {any} */ a) =>
|
|
54
54
|
a.type === 'JSXAttribute' && a.name?.name === 'columns',
|
|
55
55
|
);
|
|
56
|
-
if (hasColumns) return;
|
|
57
56
|
|
|
58
57
|
let minWidthExpr;
|
|
59
58
|
if (
|
|
@@ -67,6 +66,43 @@ export default function transformer(file, api) {
|
|
|
67
66
|
return;
|
|
68
67
|
}
|
|
69
68
|
|
|
69
|
+
if (columnsAttr) {
|
|
70
|
+
// Both `columns` and `minChildWidth` are present. This is only safely
|
|
71
|
+
// migratable when `columns` is a numeric literal (e.g. `columns={3}`).
|
|
72
|
+
//
|
|
73
|
+
// In the old (0.2.0) Grid runtime, when both props were set and
|
|
74
|
+
// `columns` was a number, `minChildWidth` dominated and the numeric
|
|
75
|
+
// `columns` became a *max column cap* with `auto-fit`:
|
|
76
|
+
// repeat(auto-fit, minmax(minChildWidth, 1fr)) capped at `columns`.
|
|
77
|
+
// The 0.3.0 `columns` object models this exactly as
|
|
78
|
+
// {minWidth, max, repeat: 'fit'}.
|
|
79
|
+
// So the lossless rewrite is:
|
|
80
|
+
// <Grid columns={3} minChildWidth={280}>
|
|
81
|
+
// -> <Grid columns={{minWidth: 280, max: 3, repeat: 'fit'}}>
|
|
82
|
+
//
|
|
83
|
+
// If `columns` is already an object or a non-numeric/dynamic
|
|
84
|
+
// expression, the combined behavior is genuinely ambiguous, so we
|
|
85
|
+
// bail and leave it unmigrated (it surfaces separately).
|
|
86
|
+
const columnsValue = columnsAttr.value;
|
|
87
|
+
if (columnsValue?.type !== 'JSXExpressionContainer') return;
|
|
88
|
+
const columnsExpr = columnsValue.expression;
|
|
89
|
+
const isNumericLiteral =
|
|
90
|
+
(columnsExpr?.type === 'NumericLiteral' ||
|
|
91
|
+
columnsExpr?.type === 'Literal') &&
|
|
92
|
+
typeof columnsExpr.value === 'number';
|
|
93
|
+
if (!isNumericLiteral) return;
|
|
94
|
+
|
|
95
|
+
const columnsObject = j.objectExpression([
|
|
96
|
+
j.property('init', j.identifier('minWidth'), minWidthExpr),
|
|
97
|
+
j.property('init', j.identifier('max'), j.literal(columnsExpr.value)),
|
|
98
|
+
j.property('init', j.identifier('repeat'), j.literal('fit')),
|
|
99
|
+
]);
|
|
100
|
+
columnsAttr.value = j.jsxExpressionContainer(columnsObject);
|
|
101
|
+
attrs.splice(attrs.indexOf(minAttr), 1);
|
|
102
|
+
hasChanges = true;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
70
106
|
const columnsObject = j.objectExpression([
|
|
71
107
|
j.property('init', j.identifier('minWidth'), minWidthExpr),
|
|
72
108
|
j.property('init', j.identifier('repeat'), j.literal('fit')),
|