@supersoniks/concorde 5.0.5 → 5.1.1
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/.claude/launch.json +15 -0
- package/.claude/vite.http.config.mjs +7 -0
- package/.taste-skill/after/desktop-fold.jpg +0 -0
- package/.taste-skill/after/desktop-page-01.jpg +0 -0
- package/.taste-skill/after/desktop-page-02.jpg +0 -0
- package/.taste-skill/after/desktop-page-03.jpg +0 -0
- package/.taste-skill/after/mobile-fold.jpg +0 -0
- package/.taste-skill/after/mobile-page-01.jpg +0 -0
- package/.taste-skill/after/report.json +4103 -0
- package/.taste-skill/after/tablet-fold.jpg +0 -0
- package/.taste-skill/after/tablet-page-01.jpg +0 -0
- package/.taste-skill/after/tablet-page-02.jpg +0 -0
- package/.taste-skill/before/desktop-fold.jpg +0 -0
- package/.taste-skill/before/desktop-page-01.jpg +0 -0
- package/.taste-skill/before/desktop-page-02.jpg +0 -0
- package/.taste-skill/before/mobile-fold.jpg +0 -0
- package/.taste-skill/before/mobile-page-01.jpg +0 -0
- package/.taste-skill/before/report.json +2556 -0
- package/.taste-skill/before/tablet-fold.jpg +0 -0
- package/.taste-skill/before/tablet-page-01.jpg +0 -0
- package/.taste-skill/direction.json +510 -0
- package/.taste-skill/notes.md +84 -0
- package/.taste-skill/prod/desktop-fold.jpg +0 -0
- package/.taste-skill/prod/desktop-page-01.jpg +0 -0
- package/.taste-skill/prod/desktop-page-02.jpg +0 -0
- package/.taste-skill/prod/desktop-page-03.jpg +0 -0
- package/.taste-skill/prod/mobile-fold.jpg +0 -0
- package/.taste-skill/prod/mobile-page-01.jpg +0 -0
- package/.taste-skill/prod/mobile-page-02.jpg +0 -0
- package/.taste-skill/prod/report.json +1973 -0
- package/.taste-skill/prod/tablet-fold.jpg +0 -0
- package/.taste-skill/prod/tablet-page-01.jpg +0 -0
- package/.taste-skill/prod/tablet-page-02.jpg +0 -0
- package/.tmp-cem/custom-elements.json +13705 -0
- package/AUDIT.md +228 -0
- package/PLAN-AMELIORATION.md +220 -0
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +196 -196
- package/concorde-core.es.js +3854 -3572
- package/custom-elements-manifest.config.mjs +14 -0
- package/dist/concorde-core.bundle.js +196 -196
- package/dist/concorde-core.es.js +3854 -3572
- package/dist/img/archive/concorde-001-2cv.webp +0 -0
- package/dist/img/archive/concorde-red-arrows-2002-poster.jpg +0 -0
- package/dist/img/archive/concorde-red-arrows-2002.webm +0 -0
- package/dist/img/archive/credits.json +28 -0
- package/docs/assets/index-Bb-_LRyY.js +12280 -0
- package/docs/index.html +1 -1
- package/index.html +27 -63
- package/package.json +44 -3
- package/public/img/archive/concorde-001-2cv.webp +0 -0
- package/public/img/archive/concorde-red-arrows-2002-poster.jpg +0 -0
- package/public/img/archive/concorde-red-arrows-2002.webm +0 -0
- package/public/img/archive/credits.json +28 -0
- package/scripts/generate-sdui-catalog.mjs +250 -0
- package/scripts/generate-sdui-catalog.spec.ts +91 -0
- package/scripts/pre-build.mjs +15 -0
- package/src/core/components/functional/fetch/fetch.md +2 -2
- package/src/core/components/functional/sdui/SDUIDescriptorTransformer.ts +5 -5
- package/src/core/components/functional/sdui/__fixtures__/descriptors.ts +251 -0
- package/src/core/components/functional/sdui/__snapshots__/sdui-action.spec.ts.snap +73 -0
- package/src/core/components/functional/sdui/__snapshots__/sdui-incremental.spec.ts.snap +50 -0
- package/src/core/components/functional/sdui/__snapshots__/sdui-partial.spec.ts.snap +50 -0
- package/src/core/components/functional/sdui/__snapshots__/sdui.regression.spec.ts.snap +461 -0
- package/src/core/components/functional/sdui/component-catalog.gaps.json +511 -0
- package/src/core/components/functional/sdui/component-catalog.json +3103 -0
- package/src/core/components/functional/sdui/component-catalog.spec.ts +23 -0
- package/src/core/components/functional/sdui/sdui-action.spec.ts +99 -0
- package/src/core/components/functional/sdui/sdui-dom-snapshot.ts +61 -0
- package/src/core/components/functional/sdui/sdui-extension-points.md +94 -0
- package/src/core/components/functional/sdui/sdui-incremental.spec.ts +137 -0
- package/src/core/components/functional/sdui/sdui-partial.spec.ts +83 -0
- package/src/core/components/functional/sdui/sdui-partial.ts +19 -0
- package/src/core/components/functional/sdui/sdui-safe.spec.ts +188 -0
- package/src/core/components/functional/sdui/sdui-safe.ts +100 -0
- package/src/core/components/functional/sdui/sdui.md +122 -0
- package/src/core/components/functional/sdui/sdui.regression.spec.ts +64 -0
- package/src/core/components/functional/sdui/sdui.ts +442 -34
- package/src/core/components/functional/sdui/types.ts +49 -0
- package/src/core/components/ui/modal/modal.ts +14 -11
- package/src/docs/_getting-started/components.md +5 -0
- package/src/docs/_getting-started/theming.md +2 -2
- package/src/docs/code.ts +88 -75
- package/src/docs/components/docs-demo-sources.ts +16 -8
- package/src/docs/components/docs-lit-demo.ts +19 -37
- package/src/docs/docs.ts +5 -0
- package/src/docs/example/docs-boarding-pass-demo.ts +134 -0
- package/src/docs/example/docs-provider-keys.ts +12 -0
- package/src/docs/header/header.ts +233 -69
- package/src/docs/home/catalog.ts +44 -0
- package/src/docs/home/home.ts +513 -0
- package/src/docs/home/mach-stage.ts +389 -0
- package/src/docs/layout.ts +3 -1
- package/src/docs/navigation/components-index.ts +56 -0
- package/src/docs/navigation/navigation.ts +240 -232
- package/src/docs/navigation/pager.ts +67 -0
- package/src/docs/navigation/toc.ts +98 -0
- package/src/docs/search/docs-search.json +330 -20
- package/src/docs/search/markdown-renderer.ts +11 -3
- package/src/docs/shiki/index.ts +109 -0
- package/src/docs/tailwind/css/base/scrollbar.css +22 -0
- package/src/docs/tailwind/css/base/theme.css +96 -0
- package/src/docs/tailwind/css/base/typography.css +4 -0
- package/src/docs/tailwind/css/docs/boarding-pass.css +65 -0
- package/src/docs/tailwind/css/docs/content.css +32 -0
- package/src/docs/tailwind/css/docs/page.css +10 -0
- package/src/docs/tailwind/css/docs.css +9 -0
- package/src/docs/tailwind/css/home/hero.css +210 -0
- package/src/docs/tailwind/css/home/motion.css +84 -0
- package/src/docs/tailwind/css/home/sections.css +416 -0
- package/src/docs/tailwind/css/tailwind.css +7 -185
- package/src/tsconfig.json +113 -2
- package/src/tsconfig.tsbuildinfo +1 -1
- package/tailwind.config.js +8 -4
- package/docs/assets/index-wVZpLVYW.js +0 -12122
- package/docs/crawl/core/components/functional/date/date.html +0 -190
- package/docs/crawl/core/components/functional/fetch/fetch.html +0 -109
- package/docs/crawl/core/components/functional/if/if.html +0 -27
- package/docs/crawl/core/components/functional/jsonata/jsonata.html +0 -79
- package/docs/crawl/core/components/functional/list/list.html +0 -70
- package/docs/crawl/core/components/functional/mix/mix.html +0 -45
- package/docs/crawl/core/components/functional/queue/queue.html +0 -96
- package/docs/crawl/core/components/functional/router/router.html +0 -106
- package/docs/crawl/core/components/functional/sdui/sdui.html +0 -269
- package/docs/crawl/core/components/functional/states/states.html +0 -66
- package/docs/crawl/core/components/functional/submit/submit.html +0 -160
- package/docs/crawl/core/components/functional/subscriber/subscriber.html +0 -67
- package/docs/crawl/core/components/functional/value/value.html +0 -51
- package/docs/crawl/core/components/ui/alert/alert.html +0 -211
- package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +0 -131
- package/docs/crawl/core/components/ui/badge/badge.html +0 -197
- package/docs/crawl/core/components/ui/button/button.html +0 -150
- package/docs/crawl/core/components/ui/captcha/captcha.html +0 -112
- package/docs/crawl/core/components/ui/card/card.html +0 -253
- package/docs/crawl/core/components/ui/divider/divider.html +0 -144
- package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +0 -73
- package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +0 -266
- package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +0 -122
- package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +0 -121
- package/docs/crawl/core/components/ui/form/input/input.html +0 -109
- package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +0 -38
- package/docs/crawl/core/components/ui/form/radio/radio.html +0 -56
- package/docs/crawl/core/components/ui/form/select/select.html +0 -73
- package/docs/crawl/core/components/ui/form/switch/switch.html +0 -56
- package/docs/crawl/core/components/ui/form/textarea/textarea.html +0 -59
- package/docs/crawl/core/components/ui/group/group.html +0 -104
- package/docs/crawl/core/components/ui/icon/icon.html +0 -222
- package/docs/crawl/core/components/ui/image/image.html +0 -298
- package/docs/crawl/core/components/ui/link/link.html +0 -100
- package/docs/crawl/core/components/ui/loader/loader.html +0 -124
- package/docs/crawl/core/components/ui/menu/menu.html +0 -394
- package/docs/crawl/core/components/ui/modal/modal.html +0 -342
- package/docs/crawl/core/components/ui/pop/pop.html +0 -296
- package/docs/crawl/core/components/ui/progress/progress.html +0 -131
- package/docs/crawl/core/components/ui/table/table.html +0 -545
- package/docs/crawl/core/components/ui/theme/theme.html +0 -131
- package/docs/crawl/core/components/ui/toast/toast.html +0 -233
- package/docs/crawl/core/components/ui/tooltip/tooltip.html +0 -140
- package/docs/crawl/docs/_core-concept/dataFlow.html +0 -177
- package/docs/crawl/docs/_core-concept/overview.html +0 -66
- package/docs/crawl/docs/_core-concept/subscriber.html +0 -75
- package/docs/crawl/docs/_decorators/ancestor-attribute.html +0 -117
- package/docs/crawl/docs/_decorators/auto-subscribe.html +0 -202
- package/docs/crawl/docs/_decorators/bind.html +0 -141
- package/docs/crawl/docs/_decorators/get.html +0 -149
- package/docs/crawl/docs/_decorators/handle.html +0 -165
- package/docs/crawl/docs/_decorators/on-assign.html +0 -355
- package/docs/crawl/docs/_decorators/patch.html +0 -78
- package/docs/crawl/docs/_decorators/post.html +0 -139
- package/docs/crawl/docs/_decorators/publish.html +0 -61
- package/docs/crawl/docs/_decorators/put.html +0 -78
- package/docs/crawl/docs/_decorators/subscribe.html +0 -109
- package/docs/crawl/docs/_decorators/wait-for-ancestors.html +0 -137
- package/docs/crawl/docs/_directives/sub.html +0 -93
- package/docs/crawl/docs/_getting-started/ai-agents.html +0 -173
- package/docs/crawl/docs/_getting-started/concorde-manual-install.html +0 -100
- package/docs/crawl/docs/_getting-started/concorde-outside.html +0 -58
- package/docs/crawl/docs/_getting-started/create-a-component.html +0 -111
- package/docs/crawl/docs/_getting-started/my-first-component.html +0 -189
- package/docs/crawl/docs/_getting-started/my-first-subscriber.html +0 -110
- package/docs/crawl/docs/_getting-started/pubsub.html +0 -62
- package/docs/crawl/docs/_getting-started/start.html +0 -76
- package/docs/crawl/docs/_getting-started/theming.html +0 -333
- package/docs/crawl/docs/_misc/api-configuration.html +0 -149
- package/docs/crawl/docs/_misc/dataProviderKey.html +0 -135
- package/docs/crawl/docs/_misc/docs-mock-api.html +0 -158
- package/docs/crawl/docs/_misc/dynamic-path.html +0 -180
- package/docs/crawl/docs/_misc/endpoint.html +0 -50
- package/docs/crawl/docs/_misc/formelement-subscriber-removal.html +0 -138
- package/docs/crawl/docs/_misc/html-integration.html +0 -48
- package/docs/crawl/hubs/decorators.html +0 -547
- package/docs/crawl/hubs/form-components.html +0 -381
- package/docs/crawl/hubs/remote-data.html +0 -452
- package/docs/crawl/index.html +0 -102
- package/docs/llms-full.txt +0 -7008
- package/docs/llms.txt +0 -97
- package/docs/sitemap.xml +0 -240
- package/docs/src/core/components/functional/date/date.md +0 -290
- package/docs/src/core/components/functional/fetch/fetch.md +0 -125
- package/docs/src/core/components/functional/if/if.md +0 -9
- package/docs/src/core/components/functional/jsonata/jsonata.md +0 -56
- package/docs/src/core/components/functional/list/list.md +0 -65
- package/docs/src/core/components/functional/mix/mix.md +0 -41
- package/docs/src/core/components/functional/queue/queue.md +0 -72
- package/docs/src/core/components/functional/router/router.md +0 -94
- package/docs/src/core/components/functional/sdui/default-library.json +0 -108
- package/docs/src/core/components/functional/sdui/example.json +0 -99
- package/docs/src/core/components/functional/sdui/sdui.md +0 -356
- package/docs/src/core/components/functional/states/states.md +0 -87
- package/docs/src/core/components/functional/submit/submit.md +0 -114
- package/docs/src/core/components/functional/subscriber/subscriber.md +0 -91
- package/docs/src/core/components/functional/value/value.md +0 -35
- package/docs/src/core/components/ui/alert/alert.md +0 -196
- package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -85
- package/docs/src/core/components/ui/badge/badge.md +0 -188
- package/docs/src/core/components/ui/button/button.md +0 -183
- package/docs/src/core/components/ui/captcha/captcha.md +0 -71
- package/docs/src/core/components/ui/card/card.md +0 -215
- package/docs/src/core/components/ui/divider/divider.md +0 -129
- package/docs/src/core/components/ui/form/checkbox/checkbox.md +0 -77
- package/docs/src/core/components/ui/form/fieldset/fieldset.md +0 -205
- package/docs/src/core/components/ui/form/form-actions/form-actions.md +0 -100
- package/docs/src/core/components/ui/form/form-layout/form-layout.md +0 -119
- package/docs/src/core/components/ui/form/input/input.md +0 -142
- package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +0 -29
- package/docs/src/core/components/ui/form/radio/radio.md +0 -57
- package/docs/src/core/components/ui/form/select/select.md +0 -71
- package/docs/src/core/components/ui/form/switch/switch.md +0 -57
- package/docs/src/core/components/ui/form/textarea/textarea.md +0 -65
- package/docs/src/core/components/ui/group/group.md +0 -75
- package/docs/src/core/components/ui/icon/icon.md +0 -214
- package/docs/src/core/components/ui/icon/icons.json +0 -1
- package/docs/src/core/components/ui/image/image.md +0 -233
- package/docs/src/core/components/ui/link/link.md +0 -81
- package/docs/src/core/components/ui/loader/loader.md +0 -112
- package/docs/src/core/components/ui/menu/menu.md +0 -370
- package/docs/src/core/components/ui/modal/modal.md +0 -235
- package/docs/src/core/components/ui/pop/pop.md +0 -294
- package/docs/src/core/components/ui/progress/progress.md +0 -109
- package/docs/src/core/components/ui/table/table.md +0 -431
- package/docs/src/core/components/ui/theme/theme.md +0 -122
- package/docs/src/core/components/ui/toast/toast.md +0 -216
- package/docs/src/core/components/ui/tooltip/tooltip.md +0 -124
- package/docs/src/docs/_core-concept/dataFlow.md +0 -88
- package/docs/src/docs/_core-concept/overview.md +0 -57
- package/docs/src/docs/_core-concept/subscriber.md +0 -75
- package/docs/src/docs/_decorators/ancestor-attribute.md +0 -128
- package/docs/src/docs/_decorators/auto-subscribe.md +0 -202
- package/docs/src/docs/_decorators/bind.md +0 -167
- package/docs/src/docs/_decorators/get.md +0 -162
- package/docs/src/docs/_decorators/handle.md +0 -175
- package/docs/src/docs/_decorators/on-assign.md +0 -390
- package/docs/src/docs/_decorators/patch.md +0 -70
- package/docs/src/docs/_decorators/post.md +0 -146
- package/docs/src/docs/_decorators/publish.md +0 -55
- package/docs/src/docs/_decorators/put.md +0 -70
- package/docs/src/docs/_decorators/subscribe.md +0 -100
- package/docs/src/docs/_decorators/wait-for-ancestors.md +0 -163
- package/docs/src/docs/_directives/sub.md +0 -91
- package/docs/src/docs/_getting-started/ai-agents.md +0 -76
- package/docs/src/docs/_getting-started/concorde-manual-install.md +0 -133
- package/docs/src/docs/_getting-started/concorde-outside.md +0 -33
- package/docs/src/docs/_getting-started/create-a-component.md +0 -139
- package/docs/src/docs/_getting-started/my-first-component.md +0 -236
- package/docs/src/docs/_getting-started/my-first-subscriber.md +0 -120
- package/docs/src/docs/_getting-started/pubsub.md +0 -37
- package/docs/src/docs/_getting-started/start.md +0 -47
- package/docs/src/docs/_getting-started/theming.md +0 -253
- package/docs/src/docs/_misc/api-configuration.md +0 -81
- package/docs/src/docs/_misc/dataProviderKey.md +0 -168
- package/docs/src/docs/_misc/docs-mock-api.md +0 -63
- package/docs/src/docs/_misc/dynamic-path.md +0 -71
- package/docs/src/docs/_misc/endpoint.md +0 -45
- package/docs/src/docs/_misc/formelement-subscriber-removal.md +0 -115
- package/docs/src/docs/_misc/html-integration.md +0 -15
- package/docs/src/docs/search/docs-search.json +0 -12242
- package/docs/src/tag-list.json +0 -1
- package/docs/src/tsconfig-model.json +0 -23
- package/docs/src/tsconfig.json +0 -1242
- package/src/docs/prism/index.ts +0 -6
- package/src/docs/prism/prism.css +0 -158
- package/src/docs/prism/prism.js +0 -1022
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
|
|
3
|
+
import {
|
|
4
|
+
markupAndInnerHTML,
|
|
5
|
+
prefixSuffix,
|
|
6
|
+
layoutSafe,
|
|
7
|
+
} from "./__fixtures__/descriptors";
|
|
8
|
+
import { snapshotLightDom } from "./sdui-dom-snapshot";
|
|
9
|
+
import {
|
|
10
|
+
validateSafeDescriptor,
|
|
11
|
+
isUnsafeSduiNode,
|
|
12
|
+
} from "./sdui-safe";
|
|
13
|
+
import type { SDUIDescriptor } from "./types";
|
|
14
|
+
import "./sdui";
|
|
15
|
+
import type { SonicSDUI } from "./sdui";
|
|
16
|
+
|
|
17
|
+
function clone<T>(value: T): T {
|
|
18
|
+
return structuredClone(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function flushSdui(elt: SonicSDUI) {
|
|
22
|
+
await elt.updateComplete;
|
|
23
|
+
await Promise.resolve();
|
|
24
|
+
await Promise.resolve();
|
|
25
|
+
await elt.updateComplete;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function render(
|
|
29
|
+
descriptor: SDUIDescriptor,
|
|
30
|
+
attrs: Record<string, string> = {}
|
|
31
|
+
): Promise<SonicSDUI> {
|
|
32
|
+
const attrStr = Object.entries(attrs)
|
|
33
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
34
|
+
.join(" ");
|
|
35
|
+
const [elt] = TestUtils.bootstrap(
|
|
36
|
+
`<sonic-sdui ${attrStr}></sonic-sdui>`
|
|
37
|
+
) as unknown as SonicSDUI[];
|
|
38
|
+
elt.props = clone(descriptor) as never;
|
|
39
|
+
await flushSdui(elt);
|
|
40
|
+
return elt;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
document.body.innerHTML = "";
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("validateSafeDescriptor", () => {
|
|
49
|
+
it("ne signale rien sur un descripteur sans HTML/JS libre", () => {
|
|
50
|
+
expect(validateSafeDescriptor(layoutSafe)).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("signale js, markup, innerHTML, prefix, suffix", () => {
|
|
54
|
+
const descriptor: SDUIDescriptor = {
|
|
55
|
+
js: ["https://example.test/x.js"],
|
|
56
|
+
library: {
|
|
57
|
+
bad: { markup: "<b>x</b>" },
|
|
58
|
+
},
|
|
59
|
+
nodes: [
|
|
60
|
+
{ innerHTML: "a" },
|
|
61
|
+
{ prefix: "p", suffix: "s", tagName: "div" },
|
|
62
|
+
{
|
|
63
|
+
tagName: "div",
|
|
64
|
+
nodes: [{ markup: "<i>nested</i>" }],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const violations = validateSafeDescriptor(descriptor);
|
|
69
|
+
expect(violations.map((v) => v.path).sort()).toEqual(
|
|
70
|
+
[
|
|
71
|
+
"js",
|
|
72
|
+
"library.bad.markup",
|
|
73
|
+
"nodes[0].innerHTML",
|
|
74
|
+
"nodes[1].prefix",
|
|
75
|
+
"nodes[1].suffix",
|
|
76
|
+
"nodes[2].nodes[0].markup",
|
|
77
|
+
].sort()
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("isUnsafeSduiNode ne regarde que le nœud courant", () => {
|
|
82
|
+
expect(isUnsafeSduiNode({ tagName: "div", nodes: [{ innerHTML: "x" }] })).toBe(
|
|
83
|
+
false
|
|
84
|
+
);
|
|
85
|
+
expect(isUnsafeSduiNode({ innerHTML: "x" })).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('sonic-sdui profile="safe"', () => {
|
|
90
|
+
it("sans profile, markup/innerHTML restent rendus (comportement historique)", async () => {
|
|
91
|
+
const elt = await render(markupAndInnerHTML);
|
|
92
|
+
const snap = snapshotLightDom(elt);
|
|
93
|
+
expect(JSON.stringify(snap)).toContain("badge");
|
|
94
|
+
expect(JSON.stringify(snap)).toContain("gras");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("ignore les nœuds markup/innerHTML avec un warning", async () => {
|
|
98
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
99
|
+
const elt = await render(markupAndInnerHTML, { profile: "safe" });
|
|
100
|
+
const snap = snapshotLightDom(elt);
|
|
101
|
+
// Seul le toast subscriber reste
|
|
102
|
+
expect(snap).toEqual([{ tag: "sonic-toast-message-subscriber" }]);
|
|
103
|
+
expect(warn).toHaveBeenCalled();
|
|
104
|
+
expect(
|
|
105
|
+
warn.mock.calls.some((c) => String(c[0]).includes('profile="safe"'))
|
|
106
|
+
).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("ignore prefix/suffix", async () => {
|
|
110
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
111
|
+
const elt = await render(prefixSuffix, { profile: "safe" });
|
|
112
|
+
expect(snapshotLightDom(elt)).toEqual([
|
|
113
|
+
{ tag: "sonic-toast-message-subscriber" },
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("laisse passer un layout sans HTML libre", async () => {
|
|
118
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
119
|
+
const elt = await render(layoutSafe, { profile: "safe" });
|
|
120
|
+
const snap = snapshotLightDom(elt);
|
|
121
|
+
expect(JSON.stringify(snap)).toContain("sdui-layout");
|
|
122
|
+
expect(JSON.stringify(snap)).toContain("sonic-input");
|
|
123
|
+
expect(JSON.stringify(snap)).toContain("Email");
|
|
124
|
+
expect(JSON.stringify(snap)).toContain("Titre safe");
|
|
125
|
+
expect(JSON.stringify(snap)).toContain("Go");
|
|
126
|
+
expect(warn).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("rend textContent en plain text (pas de HTML)", async () => {
|
|
130
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
131
|
+
const elt = await render(
|
|
132
|
+
{
|
|
133
|
+
nodes: [
|
|
134
|
+
{
|
|
135
|
+
tagName: "p",
|
|
136
|
+
textContent: "<b>not bold</b> & still text",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{ profile: "safe" }
|
|
141
|
+
);
|
|
142
|
+
const p = elt.querySelector("p");
|
|
143
|
+
expect(p?.textContent).toBe("<b>not bold</b> & still text");
|
|
144
|
+
expect(p?.querySelector("b")).toBeNull();
|
|
145
|
+
expect(warn).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("n'appelle pas loadJS pour descriptor.js", async () => {
|
|
149
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
150
|
+
const append = vi.spyOn(document.head, "appendChild");
|
|
151
|
+
await render(
|
|
152
|
+
{ js: ["https://example.test/unsafe.js"], nodes: [{ tagName: "div" }] },
|
|
153
|
+
{ profile: "safe" }
|
|
154
|
+
);
|
|
155
|
+
const scriptAppends = append.mock.calls.filter((c) => {
|
|
156
|
+
const n = c[0] as Node;
|
|
157
|
+
return n && (n as HTMLElement).tagName === "SCRIPT";
|
|
158
|
+
});
|
|
159
|
+
expect(scriptAppends).toHaveLength(0);
|
|
160
|
+
expect(
|
|
161
|
+
warn.mock.calls.some((c) => String(c[0]).includes("chargement js ignoré"))
|
|
162
|
+
).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("ignore un enfant dangereux sans casser le parent", async () => {
|
|
166
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
167
|
+
const elt = await render(
|
|
168
|
+
{
|
|
169
|
+
nodes: [
|
|
170
|
+
{
|
|
171
|
+
tagName: "div",
|
|
172
|
+
attributes: { class: "parent" },
|
|
173
|
+
nodes: [
|
|
174
|
+
{ tagName: "span", attributes: { class: "ok" }, nodes: [] },
|
|
175
|
+
{ innerHTML: "<img src=x onerror=alert(1)>" },
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{ profile: "safe" }
|
|
181
|
+
);
|
|
182
|
+
const html = JSON.stringify(snapshotLightDom(elt));
|
|
183
|
+
expect(html).toContain('"class":"parent"');
|
|
184
|
+
expect(html).toContain('"class":"ok"');
|
|
185
|
+
expect(html).not.toContain("onerror");
|
|
186
|
+
expect(html).not.toContain("img");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { SDUIDescriptor, SDUINode } from "./types";
|
|
2
|
+
|
|
3
|
+
/** Champs de nœud refusés en profil safe (injection HTML / scripts). */
|
|
4
|
+
export const SAFE_FORBIDDEN_NODE_FIELDS = [
|
|
5
|
+
"markup",
|
|
6
|
+
"innerHTML",
|
|
7
|
+
"prefix",
|
|
8
|
+
"suffix",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type SafeForbiddenNodeField = (typeof SAFE_FORBIDDEN_NODE_FIELDS)[number];
|
|
12
|
+
|
|
13
|
+
export type SafeDescriptorViolation = {
|
|
14
|
+
/** Chemin JSON-like, ex. `js`, `nodes[0].markup`, `library.card.innerHTML` */
|
|
15
|
+
path: string;
|
|
16
|
+
/** Champ en cause */
|
|
17
|
+
field: "js" | SafeForbiddenNodeField;
|
|
18
|
+
/** Message lisible */
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function nodeForbiddenFields(node: SDUINode): SafeForbiddenNodeField[] {
|
|
23
|
+
const found: SafeForbiddenNodeField[] = [];
|
|
24
|
+
for (const field of SAFE_FORBIDDEN_NODE_FIELDS) {
|
|
25
|
+
const value = node[field];
|
|
26
|
+
if (value != null && value !== "") found.push(field);
|
|
27
|
+
}
|
|
28
|
+
return found;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collectNodeViolations(
|
|
32
|
+
node: SDUINode,
|
|
33
|
+
path: string,
|
|
34
|
+
out: SafeDescriptorViolation[]
|
|
35
|
+
) {
|
|
36
|
+
const fields = nodeForbiddenFields(node);
|
|
37
|
+
for (const field of fields) {
|
|
38
|
+
out.push({
|
|
39
|
+
path: `${path}.${field}`,
|
|
40
|
+
field,
|
|
41
|
+
message: `Champ "${field}" interdit en profil safe (${path}).`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (node.nodes) {
|
|
45
|
+
node.nodes.forEach((child, i) => {
|
|
46
|
+
collectNodeViolations(child, `${path}.nodes[${i}]`, out);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Valide un descripteur SDUI pour le profil `safe`.
|
|
53
|
+
* Fonction pure : aucun effet de bord, réutilisable côté serveur.
|
|
54
|
+
*/
|
|
55
|
+
export function validateSafeDescriptor(
|
|
56
|
+
descriptor: SDUIDescriptor | null | undefined
|
|
57
|
+
): SafeDescriptorViolation[] {
|
|
58
|
+
const violations: SafeDescriptorViolation[] = [];
|
|
59
|
+
if (!descriptor) return violations;
|
|
60
|
+
|
|
61
|
+
if (descriptor.js && descriptor.js.length > 0) {
|
|
62
|
+
violations.push({
|
|
63
|
+
path: "js",
|
|
64
|
+
field: "js",
|
|
65
|
+
message: `Chargement de scripts (js) interdit en profil safe (${descriptor.js.length} URL).`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (descriptor.library) {
|
|
70
|
+
for (const key of Object.keys(descriptor.library)) {
|
|
71
|
+
collectNodeViolations(
|
|
72
|
+
descriptor.library[key],
|
|
73
|
+
`library.${key}`,
|
|
74
|
+
violations
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (descriptor.nodes) {
|
|
80
|
+
descriptor.nodes.forEach((node, i) => {
|
|
81
|
+
collectNodeViolations(node, `nodes[${i}]`, violations);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return violations;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** True si le nœud lui-même (pas ses enfants) porte un champ interdit. */
|
|
89
|
+
export function isUnsafeSduiNode(node: SDUINode): boolean {
|
|
90
|
+
return nodeForbiddenFields(node).length > 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function formatSafeNodeWarning(path: string, node: SDUINode): string {
|
|
94
|
+
const fields = nodeForbiddenFields(node);
|
|
95
|
+
return `sonic-sdui (profile="safe") : nœud ignoré (${path}) — champs refusés : ${fields.join(", ")}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function formatSafeJsWarning(urls: string[]): string {
|
|
99
|
+
return `sonic-sdui (profile="safe") : chargement js ignoré (${urls.length} URL) — ${urls.join(", ")}`;
|
|
100
|
+
}
|
|
@@ -24,6 +24,119 @@ As a fetcher, invalidating the publisher or updating the endpoint will trigger a
|
|
|
24
24
|
|
|
25
25
|
It has no shadowdom, and its display style is set to `contents`, so an empty SDUI has the fewest impact on the layout.
|
|
26
26
|
|
|
27
|
+
### Safe profile (opt-in)
|
|
28
|
+
|
|
29
|
+
Set `profile="safe"` on `sonic-sdui` to refuse untrusted HTML/JS from the descriptor:
|
|
30
|
+
|
|
31
|
+
* `js` URLs are **not** loaded
|
|
32
|
+
* nodes that use `markup`, `innerHTML`, `prefix`, or `suffix` are **skipped** (not rendered)
|
|
33
|
+
* `textContent` is **allowed** (plain text, no HTML parsing)
|
|
34
|
+
* a clear `console.warn` is emitted for each refusal
|
|
35
|
+
* without the attribute, behaviour is unchanged
|
|
36
|
+
|
|
37
|
+
Use the pure helper `validateSafeDescriptor(descriptor)` (from `sdui-safe`) to list violations server-side or in a custom validator — it returns `{ path, field, message }[]` and has no side effects.
|
|
38
|
+
|
|
39
|
+
<sonic-code>
|
|
40
|
+
<template>
|
|
41
|
+
<sonic-sdui
|
|
42
|
+
profile="safe"
|
|
43
|
+
dataProvider="sdui-safe-example"
|
|
44
|
+
props='{
|
|
45
|
+
"nodes":[
|
|
46
|
+
{ "tagName":"p", "textContent":"Plain text is fine" },
|
|
47
|
+
{ "innerHTML":"<script>/* refused */</script>" }
|
|
48
|
+
]
|
|
49
|
+
}'
|
|
50
|
+
></sonic-sdui>
|
|
51
|
+
</template>
|
|
52
|
+
</sonic-code>
|
|
53
|
+
|
|
54
|
+
### Component catalog (build-only)
|
|
55
|
+
|
|
56
|
+
At build time, Concorde generates a **neutral JSON catalog** of custom elements for SDUI / external agents:
|
|
57
|
+
|
|
58
|
+
* file: `src/core/components/functional/sdui/component-catalog.json`
|
|
59
|
+
* import: `@supersoniks/concorde/sdui/component-catalog.json` (or the long `core/components/functional/sdui/…` path)
|
|
60
|
+
* regenerate: `yarn generate:sdui-catalog` (also runs in `prebuild`)
|
|
61
|
+
* source: Custom Elements Manifest via `@custom-elements-manifest/analyzer` (Lit)
|
|
62
|
+
* no runtime impact
|
|
63
|
+
|
|
64
|
+
Each entry includes `tag`, `props` (name, attribute, type, allowed `values` when detectable, `default`, description), plus slots/events when present.
|
|
65
|
+
|
|
66
|
+
Components with thin JSDoc are listed in `component-catalog.gaps.json` (same folder) — annotations are **not** mass-rewritten in this step.
|
|
67
|
+
|
|
68
|
+
### Incremental operations (opt-in)
|
|
69
|
+
|
|
70
|
+
Beside the full-descriptor rebuild path, `sonic-sdui` exposes surgical ops (siblings keep DOM state):
|
|
71
|
+
|
|
72
|
+
* `upsertNode(node, parentId?, index?)` — `node.nodeId` required
|
|
73
|
+
* `removeNode(nodeId)`
|
|
74
|
+
* `setData(path, value)` — updates a publisher path (`form.email` or `provider.nested`)
|
|
75
|
+
* `applyOp(op)` / optional `opsDataProvider` for a stream of `SDUIOp`
|
|
76
|
+
* `endIncremental()` — return to props → full rebuild
|
|
77
|
+
|
|
78
|
+
Public extension surface (also covered above): `profile="safe"`, `textContent`, `sdui-action` / `action`, component catalog JSON, `partial` + `childNodeIds` / `nodeById`, and these incremental ops.
|
|
79
|
+
|
|
80
|
+
### Partial rendering (opt-in)
|
|
81
|
+
|
|
82
|
+
Set the boolean attribute `partial` on `sonic-sdui` to tolerate missing child references:
|
|
83
|
+
|
|
84
|
+
* Declare a flat registry with `nodeById` and point to children with `childNodeIds: ["a", "b"]`
|
|
85
|
+
* If a `childNodeId` is **missing** and `partial` is set → a light placeholder is inserted
|
|
86
|
+
(`data-sdui-placeholder`, `data-node-id`, `display: contents`, `aria-hidden`)
|
|
87
|
+
* If `partial` is **not** set → `console.warn` and the missing child is skipped (no crash)
|
|
88
|
+
* Nested `nodes` arrays keep working as before; without `childNodeIds` / `partial`, behaviour is unchanged
|
|
89
|
+
* Incremental ops (later) reuse the same placeholder path
|
|
90
|
+
|
|
91
|
+
<sonic-code>
|
|
92
|
+
<template>
|
|
93
|
+
<sonic-sdui
|
|
94
|
+
partial
|
|
95
|
+
dataProvider="sdui-partial-example"
|
|
96
|
+
props='{
|
|
97
|
+
"nodes":[{
|
|
98
|
+
"tagName":"div",
|
|
99
|
+
"childNodeIds":["title", "body"]
|
|
100
|
+
}],
|
|
101
|
+
"nodeById":{
|
|
102
|
+
"title":{ "nodeId":"title", "tagName":"h2", "textContent":"Hello" }
|
|
103
|
+
}
|
|
104
|
+
}'
|
|
105
|
+
></sonic-sdui>
|
|
106
|
+
</template>
|
|
107
|
+
</sonic-code>
|
|
108
|
+
|
|
109
|
+
### Actions (`sdui-action`)
|
|
110
|
+
|
|
111
|
+
Declarative opt-in on a node: set `action: { name, context? }` (and optionally `nodeId`).
|
|
112
|
+
A click on the rendered element dispatches a `CustomEvent` named **`sdui-action`** on `sonic-sdui` with:
|
|
113
|
+
|
|
114
|
+
* `detail: { name, context, sourceNodeId? }`
|
|
115
|
+
* `bubbles: true`, `composed: true` — so a host (page shell, parent) can listen without binding each node
|
|
116
|
+
|
|
117
|
+
This does **not** replace form submit (`sonic-submit` / POST / `messagesKey`). Those keep working as before; only nodes that declare `action` emit `sdui-action`.
|
|
118
|
+
|
|
119
|
+
<sonic-code>
|
|
120
|
+
<template>
|
|
121
|
+
<sonic-sdui
|
|
122
|
+
dataProvider="sdui-action-example"
|
|
123
|
+
props='{
|
|
124
|
+
"nodes":[{
|
|
125
|
+
"tagName":"sonic-button",
|
|
126
|
+
"nodeId":"confirm-btn",
|
|
127
|
+
"textContent":"Confirm",
|
|
128
|
+
"action":{ "name":"confirm", "context":{ "step":1 } }
|
|
129
|
+
}]
|
|
130
|
+
}'
|
|
131
|
+
></sonic-sdui>
|
|
132
|
+
<script type="module">
|
|
133
|
+
document.querySelector("sonic-sdui")?.addEventListener("sdui-action", (e) => {
|
|
134
|
+
console.log(e.detail); // { name, context, sourceNodeId }
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
</template>
|
|
138
|
+
</sonic-code>
|
|
139
|
+
|
|
27
140
|
## SDUI descriptor
|
|
28
141
|
|
|
29
142
|
### TypeScript definition:
|
|
@@ -34,6 +147,7 @@ It has no shadowdom, and its display style is set to `contents`, so an empty SDU
|
|
|
34
147
|
css?: Array<string>;
|
|
35
148
|
library?: Record<string, SDUINode>;
|
|
36
149
|
nodes?: Array<SDUINode>;
|
|
150
|
+
nodeById?: Record<string, SDUINode>;
|
|
37
151
|
};
|
|
38
152
|
</template>
|
|
39
153
|
</sonic-code>
|
|
@@ -44,6 +158,7 @@ It has no shadowdom, and its display style is set to `contents`, so an empty SDU
|
|
|
44
158
|
* **css**: An array of URLs pointing to CSS files to load.
|
|
45
159
|
* **library**: A record of SDUINode definitions intended to be used as a model for other nodes. The keys of the record can be used as the libraryKey of the node. This node will then specialize the model with its own descriptor.
|
|
46
160
|
* **nodes**: An array of SDUINode descriptors. Each SDUINode will result in an HTMLElement added to the root of the component.
|
|
161
|
+
* **nodeById**: Optional flat map of nodes by `nodeId`, used with `childNodeIds` for partial / incremental graphs.
|
|
47
162
|
|
|
48
163
|
## SDUINode
|
|
49
164
|
|
|
@@ -60,12 +175,15 @@ TypeScript definition:
|
|
|
60
175
|
tagName?: string;
|
|
61
176
|
attributes?: Record<string, string>;
|
|
62
177
|
nodes?: Array<SDUINode>;
|
|
178
|
+
textContent?: string;
|
|
63
179
|
innerHTML?: string;
|
|
64
180
|
prefix?: string;
|
|
65
181
|
suffix?: string;
|
|
66
182
|
libraryKey?: string;
|
|
67
183
|
contentElementSelector?: string;
|
|
68
184
|
parentElementSelector?: string;
|
|
185
|
+
action?: { name: string; context?: Record<string, unknown> };
|
|
186
|
+
nodeId?: string;
|
|
69
187
|
};
|
|
70
188
|
</template>
|
|
71
189
|
</sonic-code>
|
|
@@ -75,12 +193,16 @@ Summary of properties:
|
|
|
75
193
|
* **tagName**: The tag name of the HTML element.
|
|
76
194
|
* **attributes**: A string key/value pair storing each attribute name/value of the HTML element created.
|
|
77
195
|
* **nodes**: An array of SDUINode descriptors. The children of the HTMLElement are created this way.
|
|
196
|
+
* **textContent**: Plain text for the node (no HTML parsing). Preferred for safe UIs; ignored when `nodes` is non-empty. Allowed with `profile="safe"`.
|
|
78
197
|
* **innerHTML**: The inner HTML of the current node is defined here if needed.
|
|
79
198
|
* **prefix**: Use it if you want to wrap the component with some HTML string in conjunction with the suffix.
|
|
80
199
|
* **suffix**: Use it if you want to wrap the component with some HTML string in conjunction with the prefix.
|
|
81
200
|
* **libraryKey**: An identifier that points to an SDUINode to be used as a model for this node.
|
|
82
201
|
* **contentElementSelector**: For SDUINodes defined in the library only. It is a CSS selector that defines where child nodes are added.
|
|
83
202
|
* **parentElementSelector**: If the current node, as a future child, has a `parentElementSelector` attribute, it is added to the node corresponding to the associated CSS selector rather than directly to the element.
|
|
203
|
+
* **action**: Optional `{ name, context? }`. Click emits `sdui-action` (see above).
|
|
204
|
+
* **nodeId**: Optional logical id (not the DOM `id` attribute). Copied into `sdui-action.detail.sourceNodeId` when set.
|
|
205
|
+
* **childNodeIds**: Optional list of `nodeId`s to append as children (from `nodeById` / tree index). See Partial rendering.
|
|
84
206
|
|
|
85
207
|
### Markup
|
|
86
208
|
Use it if you prefer to define your node entirely using an HTML string.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
|
|
3
|
+
import SDUIDescriptorTransformer from "./SDUIDescriptorTransformer";
|
|
4
|
+
import {
|
|
5
|
+
renderFixtures,
|
|
6
|
+
transformRemapDescription,
|
|
7
|
+
transformRemapInput,
|
|
8
|
+
} from "./__fixtures__/descriptors";
|
|
9
|
+
import { snapshotLightDom } from "./sdui-dom-snapshot";
|
|
10
|
+
import type { SDUIDescriptor } from "./types";
|
|
11
|
+
import "./sdui";
|
|
12
|
+
import type { SonicSDUI } from "./sdui";
|
|
13
|
+
|
|
14
|
+
function clone<T>(value: T): T {
|
|
15
|
+
return structuredClone(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Attendre la fin de updateContents (await loadLibrary / transform). */
|
|
19
|
+
async function flushSdui(elt: SonicSDUI) {
|
|
20
|
+
await elt.updateComplete;
|
|
21
|
+
await Promise.resolve();
|
|
22
|
+
await Promise.resolve();
|
|
23
|
+
await elt.updateComplete;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function renderDescriptor(descriptor: SDUIDescriptor): Promise<SonicSDUI> {
|
|
27
|
+
const [elt] = TestUtils.bootstrap(
|
|
28
|
+
`<sonic-sdui></sonic-sdui>`
|
|
29
|
+
) as unknown as SonicSDUI[];
|
|
30
|
+
// Clone : parseRootNodes mute nodes (toast) et updateContents mute library
|
|
31
|
+
elt.props = clone(descriptor) as never;
|
|
32
|
+
await flushSdui(elt);
|
|
33
|
+
return elt;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
document.body.innerHTML = "";
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("sonic-sdui non-régression (DOM snapshots)", () => {
|
|
41
|
+
for (const [name, descriptor] of Object.entries(renderFixtures)) {
|
|
42
|
+
it(`fixture "${name}"`, async () => {
|
|
43
|
+
const elt = await renderDescriptor(descriptor);
|
|
44
|
+
expect(snapshotLightDom(elt)).toMatchSnapshot();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("SDUIDescriptorTransformer non-régression", () => {
|
|
50
|
+
it("remap legacy-button → sonic-button (descripteur)", async () => {
|
|
51
|
+
const descriptor = clone(transformRemapInput);
|
|
52
|
+
const transformer = new SDUIDescriptorTransformer();
|
|
53
|
+
await transformer.transform(descriptor, clone(transformRemapDescription));
|
|
54
|
+
expect(descriptor).toMatchSnapshot();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("remap puis rendu DOM", async () => {
|
|
58
|
+
const descriptor = clone(transformRemapInput);
|
|
59
|
+
const transformer = new SDUIDescriptorTransformer();
|
|
60
|
+
await transformer.transform(descriptor, clone(transformRemapDescription));
|
|
61
|
+
const elt = await renderDescriptor(descriptor);
|
|
62
|
+
expect(snapshotLightDom(elt)).toMatchSnapshot();
|
|
63
|
+
});
|
|
64
|
+
});
|