@multiplatform.one/components 7.5.0 → 7.6.0
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/dist/cjs/CopyField/index.cjs +4 -1
- package/dist/cjs/CopyField/index.native.js +4 -1
- package/dist/cjs/CopyField/index.native.js.map +1 -1
- package/dist/cjs/ProgressSteps/index.cjs +5 -2
- package/dist/cjs/ProgressSteps/index.native.js +5 -2
- package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
- package/dist/cjs/Separator/index.native.js.map +1 -1
- package/dist/cjs/Wizard/index.cjs +1 -0
- package/dist/cjs/Wizard/index.native.js +3 -12
- package/dist/cjs/Wizard/index.native.js.map +1 -1
- package/dist/cjs/code/CodeBlock/index.cjs +38 -22
- package/dist/cjs/code/CodeBlock/index.native.js +38 -22
- package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.native.js +1 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/surfaces.cjs +30 -7
- package/dist/cjs/surfaces.native.js +30 -7
- package/dist/cjs/surfaces.native.js.map +1 -1
- package/dist/cjs/views/index.cjs +3 -0
- package/dist/cjs/views/index.native.js +3 -0
- package/dist/cjs/views/index.native.js.map +1 -1
- package/dist/esm/Accordion/index.native.js.map +1 -1
- package/dist/esm/Alert/index.native.js.map +1 -1
- package/dist/esm/AnimateHeight/index.native.js.map +1 -1
- package/dist/esm/Avatar/getInitials.native.js.map +1 -1
- package/dist/esm/Avatar/index.native.js.map +1 -1
- package/dist/esm/Badge/index.native.js.map +1 -1
- package/dist/esm/Button.native.js.map +1 -1
- package/dist/esm/Card.native.js.map +1 -1
- package/dist/esm/CardFooter.native.js.map +1 -1
- package/dist/esm/CardHeader.native.js.map +1 -1
- package/dist/esm/Carousel/index.native.js.map +1 -1
- package/dist/esm/Chip/index.native.js.map +1 -1
- package/dist/esm/ContextMenu/index.native.js.map +1 -1
- package/dist/esm/CopyField/index.mjs +4 -1
- package/dist/esm/CopyField/index.mjs.map +1 -1
- package/dist/esm/CopyField/index.native.js +4 -1
- package/dist/esm/CopyField/index.native.js.map +1 -1
- package/dist/esm/DotIndicator/index.native.js.map +1 -1
- package/dist/esm/DropdownMenu/index.native.js.map +1 -1
- package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
- package/dist/esm/Heading.native.js.map +1 -1
- package/dist/esm/Meter/index.native.js.map +1 -1
- package/dist/esm/Meter/meter.native.js.map +1 -1
- package/dist/esm/Pagination.native.js.map +1 -1
- package/dist/esm/ProgressSteps/index.mjs +5 -2
- package/dist/esm/ProgressSteps/index.mjs.map +1 -1
- package/dist/esm/ProgressSteps/index.native.js +5 -2
- package/dist/esm/ProgressSteps/index.native.js.map +1 -1
- package/dist/esm/Quote/index.native.js.map +1 -1
- package/dist/esm/Separator/index.mjs.map +1 -1
- package/dist/esm/Separator/index.native.js.map +1 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/SkipLink/index.native.js.map +1 -1
- package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
- package/dist/esm/SortableList/index.native.js.map +1 -1
- package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
- package/dist/esm/Tabs/index.native.js.map +1 -1
- package/dist/esm/Text.native.js.map +1 -1
- package/dist/esm/Toast/index.native.js.map +1 -1
- package/dist/esm/Toast/store.native.js.map +1 -1
- package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
- package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
- package/dist/esm/TreeSelect/index.native.js.map +1 -1
- package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
- package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
- package/dist/esm/Video/VideoFrame.native.js.map +1 -1
- package/dist/esm/Video/types.native.js.map +1 -1
- package/dist/esm/Wheel/index.native.js.map +1 -1
- package/dist/esm/Wizard/index.mjs +1 -0
- package/dist/esm/Wizard/index.mjs.map +1 -1
- package/dist/esm/Wizard/index.native.js +3 -12
- package/dist/esm/Wizard/index.native.js.map +1 -1
- package/dist/esm/actions/ActionBar.native.js.map +1 -1
- package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
- package/dist/esm/actions/devWarn.native.js.map +1 -1
- package/dist/esm/actions/index.native.js.map +1 -1
- package/dist/esm/actions/severity.native.js.map +1 -1
- package/dist/esm/charts/Axis.native.js.map +1 -1
- package/dist/esm/charts/BarChart.native.js.map +1 -1
- package/dist/esm/charts/ChartSurface.native.js.map +1 -1
- package/dist/esm/charts/LineChart.native.js.map +1 -1
- package/dist/esm/charts/PieChart.native.js.map +1 -1
- package/dist/esm/charts/Sparkline.native.js.map +1 -1
- package/dist/esm/charts/accessibility.native.js.map +1 -1
- package/dist/esm/charts/composeChart.native.js.map +1 -1
- package/dist/esm/charts/index.native.js.map +1 -1
- package/dist/esm/charts/math.native.js.map +1 -1
- package/dist/esm/charts/series.native.js.map +1 -1
- package/dist/esm/charts/svg.native.js.map +1 -1
- package/dist/esm/charts/types.native.js.map +1 -1
- package/dist/esm/charts/useChartTheme.native.js.map +1 -1
- package/dist/esm/code/Code/index.native.js.map +1 -1
- package/dist/esm/code/CodeBlock/index.mjs +42 -26
- package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
- package/dist/esm/code/CodeBlock/index.native.js +42 -26
- package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
- package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
- package/dist/esm/code/Highlighter/index.native.js.map +1 -1
- package/dist/esm/code/Pre/index.native.js.map +1 -1
- package/dist/esm/code/index.native.js.map +1 -1
- package/dist/esm/componentColors.native.js.map +1 -1
- package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
- package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
- package/dist/esm/feedback/copy.native.js.map +1 -1
- package/dist/esm/feedback/index.native.js.map +1 -1
- package/dist/esm/feedback/notify.native.js.map +1 -1
- package/dist/esm/feedback/route.native.js.map +1 -1
- package/dist/esm/feedback/store.native.js.map +1 -1
- package/dist/esm/hooks/index.native.js.map +1 -1
- package/dist/esm/hooks/useClipboard.native.js.map +1 -1
- package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
- package/dist/esm/icons/CheckRegular.native.js.map +1 -1
- package/dist/esm/icons/MinusRegular.native.js.map +1 -1
- package/dist/esm/icons/index.native.js.map +1 -1
- package/dist/esm/icons/lucide.native.js.map +1 -1
- package/dist/esm/icons/themed.native.js.map +1 -1
- package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
- package/dist/esm/images/Image/shared.native.js.map +1 -1
- package/dist/esm/images/Image/types.native.js.map +1 -1
- package/dist/esm/images/index.native.js.map +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +1 -0
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/knobImmuneExports.native.js.map +1 -1
- package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
- package/dist/esm/layouts/Debug/index.native.js.map +1 -1
- package/dist/esm/layouts/EmptyState.native.js.map +1 -1
- package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
- package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
- package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
- package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
- package/dist/esm/layouts/attachments.native.js.map +1 -1
- package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
- package/dist/esm/layouts/index.native.js.map +1 -1
- package/dist/esm/layouts/page.native.js.map +1 -1
- package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
- package/dist/esm/layouts/sidebar.native.js.map +1 -1
- package/dist/esm/layouts/tags.native.js.map +1 -1
- package/dist/esm/layouts/timeline.native.js.map +1 -1
- package/dist/esm/layouts/toolbar.native.js.map +1 -1
- package/dist/esm/shared/actionScan.native.js.map +1 -1
- package/dist/esm/shared/i18n.native.js.map +1 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/esm/shared/textRidesText.native.js.map +1 -1
- package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/esm/surfaces.mjs +31 -8
- package/dist/esm/surfaces.mjs.map +1 -1
- package/dist/esm/surfaces.native.js +31 -8
- package/dist/esm/surfaces.native.js.map +1 -1
- package/dist/esm/tamagui.native.js.map +1 -1
- package/dist/esm/testSetup.native.js.map +1 -1
- package/dist/esm/utils/setTinted.native.js.map +1 -1
- package/dist/esm/utils/unwrapText.native.js.map +1 -1
- package/dist/esm/views/Calendar.native.js.map +1 -1
- package/dist/esm/views/Dashboard.native.js.map +1 -1
- package/dist/esm/views/Gantt.native.js.map +1 -1
- package/dist/esm/views/ImageGrid.native.js.map +1 -1
- package/dist/esm/views/List.native.js.map +1 -1
- package/dist/esm/views/ReportBuilder.native.js.map +1 -1
- package/dist/esm/views/TreeView.native.js.map +1 -1
- package/dist/esm/views/calendarMath.native.js.map +1 -1
- package/dist/esm/views/ganttMath.native.js.map +1 -1
- package/dist/esm/views/index.mjs +2 -2
- package/dist/esm/views/index.mjs.map +1 -1
- package/dist/esm/views/index.native.js +2 -2
- package/dist/esm/views/index.native.js.map +1 -1
- package/dist/esm/views/kanbanDnd.native.js.map +1 -1
- package/dist/esm/views/treeViewDnd.native.js.map +1 -1
- package/dist/esm/views/treeViewLayout.native.js.map +1 -1
- package/dist/esm/views/treeViewMotion.native.js.map +1 -1
- package/dist/jsx/CopyField/index.mjs +4 -1
- package/dist/jsx/CopyField/index.mjs.map +1 -1
- package/dist/jsx/CopyField/index.native.js +4 -1
- package/dist/jsx/CopyField/index.native.js.map +1 -1
- package/dist/jsx/ProgressSteps/index.mjs +5 -2
- package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
- package/dist/jsx/ProgressSteps/index.native.js +5 -2
- package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
- package/dist/jsx/Separator/index.mjs.map +1 -1
- package/dist/jsx/Separator/index.native.js.map +1 -1
- package/dist/jsx/Wizard/index.mjs +1 -0
- package/dist/jsx/Wizard/index.mjs.map +1 -1
- package/dist/jsx/Wizard/index.native.js +3 -12
- package/dist/jsx/Wizard/index.native.js.map +1 -1
- package/dist/jsx/code/CodeBlock/index.mjs +42 -26
- package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
- package/dist/jsx/code/CodeBlock/index.native.js +38 -22
- package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +1 -0
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +1 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/surfaces.mjs +31 -8
- package/dist/jsx/surfaces.mjs.map +1 -1
- package/dist/jsx/surfaces.native.js +30 -7
- package/dist/jsx/surfaces.native.js.map +1 -1
- package/dist/jsx/views/index.mjs +2 -2
- package/dist/jsx/views/index.mjs.map +1 -1
- package/dist/jsx/views/index.native.js +3 -0
- package/dist/jsx/views/index.native.js.map +1 -1
- package/package.json +9 -9
- package/src/CopyField/index.tsx +10 -15
- package/src/Heading.native.spec.tsx +156 -0
- package/src/Heading.spec.tsx +14 -79
- package/src/Meter/Meter.spec.tsx +9 -1
- package/src/ProgressSteps/index.tsx +18 -3
- package/src/Separator/Separator.spec.tsx +68 -32
- package/src/Separator/index.tsx +27 -9
- package/src/Text.native.spec.tsx +144 -0
- package/src/Text.spec.tsx +12 -70
- package/src/Tooltip/tooltipGapContract.spec.ts +54 -0
- package/src/Wizard/Wizard.spec.tsx +23 -1
- package/src/Wizard/index.tsx +1 -8
- package/src/code/CodeBlock/CodeBlock.spec.tsx +6 -6
- package/src/code/CodeBlock/index.tsx +44 -27
- package/src/index.ts +12 -6
- package/src/overlayElevation.spec.tsx +154 -0
- package/src/surfaces.spec.tsx +98 -1
- package/src/surfaces.tsx +28 -6
- package/src/tamagui.spec.ts +11 -2
- package/src/tamagui.ts +5 -5
- package/src/views/index.ts +15 -0
- package/types/CopyField/index.d.ts +8 -14
- package/types/CopyField/index.d.ts.map +1 -1
- package/types/ProgressSteps/index.d.ts +8 -1
- package/types/ProgressSteps/index.d.ts.map +1 -1
- package/types/Separator/index.d.ts +27 -9
- package/types/Separator/index.d.ts.map +1 -1
- package/types/Tabs/index.d.ts +1 -1
- package/types/Wizard/index.d.ts.map +1 -1
- package/types/code/CodeBlock/index.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/surfaces.d.ts +6 -6
- package/types/surfaces.d.ts.map +1 -1
- package/types/views/index.d.ts +1 -1
- package/types/views/index.d.ts.map +1 -1
|
@@ -53,6 +53,13 @@ export interface ProgressStepsProps extends Omit<GetProps<typeof StepsRoot>, "ch
|
|
|
53
53
|
linear?: boolean;
|
|
54
54
|
/** Compact markers + type (density compact override). */
|
|
55
55
|
compact?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Rail label treatment. `"hidden"` keeps markers (and a11y names) and
|
|
58
|
+
* drops the visible per-step label — the 390 wizard treatment; the
|
|
59
|
+
* current step's name rides the wizard legend as "Step n of m — X".
|
|
60
|
+
* @default "visible"
|
|
61
|
+
*/
|
|
62
|
+
labels?: "visible" | "hidden";
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
type StepState = "completed" | "current" | "upcoming";
|
|
@@ -130,6 +137,7 @@ export function ProgressSteps({
|
|
|
130
137
|
allowJump = true,
|
|
131
138
|
linear = true,
|
|
132
139
|
compact,
|
|
140
|
+
labels = "visible",
|
|
133
141
|
...props
|
|
134
142
|
}: ProgressStepsProps) {
|
|
135
143
|
const { t } = useTranslation();
|
|
@@ -155,7 +163,13 @@ export function ProgressSteps({
|
|
|
155
163
|
const gapToken = knobProps.gap.gap;
|
|
156
164
|
|
|
157
165
|
return (
|
|
158
|
-
<StepsRoot
|
|
166
|
+
<StepsRoot
|
|
167
|
+
role="list"
|
|
168
|
+
aria-label={t("Progress")}
|
|
169
|
+
data-density={knobProps.density}
|
|
170
|
+
data-labels={labels}
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
159
173
|
<XStack
|
|
160
174
|
flexDirection={horizontal ? "row" : "column"}
|
|
161
175
|
alignItems={horizontal ? "flex-start" : "stretch"}
|
|
@@ -310,10 +324,11 @@ export function ProgressSteps({
|
|
|
310
324
|
/>
|
|
311
325
|
) : null;
|
|
312
326
|
|
|
327
|
+
const showLabels = labels !== "hidden";
|
|
313
328
|
const body = horizontal ? (
|
|
314
329
|
<YStack alignItems="center" flexShrink={0}>
|
|
315
330
|
{markerTarget}
|
|
316
|
-
{labelBlock}
|
|
331
|
+
{showLabels ? labelBlock : null}
|
|
317
332
|
</YStack>
|
|
318
333
|
) : (
|
|
319
334
|
<XStack alignItems="stretch" gap={gapToken} minHeight={MIN_PRESS_TARGET}>
|
|
@@ -321,7 +336,7 @@ export function ProgressSteps({
|
|
|
321
336
|
{markerTarget}
|
|
322
337
|
{verticalConnector}
|
|
323
338
|
</YStack>
|
|
324
|
-
{labelBlock}
|
|
339
|
+
{showLabels ? labelBlock : null}
|
|
325
340
|
</XStack>
|
|
326
341
|
);
|
|
327
342
|
|
|
@@ -3,17 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* MPO-47 — Separator paints a line.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* What this locks: the house rule paints, at 1 CSS px, in $borderColor, in
|
|
7
|
+
* both schemes, and cannot flex-grow (DG-OVF-07 — the raw primitive it
|
|
8
|
+
* replaced declares `flex: 1`). Asserting a separator's height and never its
|
|
9
|
+
* colour is how a transparent rule ships, so this spec asserts the painted
|
|
10
|
+
* colour first.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* (The ticket's "96 of 96 paint nothing" reading was a probe artefact — it
|
|
13
|
+
* read borderTopWidth on a primitive that paints borderBottomWidth. See the
|
|
14
|
+
* component header. The assertions below are unaffected: they are about what
|
|
15
|
+
* the HOUSE rule paints, not about what the raw one did.)
|
|
16
|
+
*
|
|
17
|
+
* jsdom cannot CASCADE Tamagui's class-based CSS, so `getComputedStyle` on the
|
|
18
|
+
* element reads nothing — but Tamagui does INJECT its atomic rules into
|
|
19
|
+
* `document.styleSheets`, so the declaration behind each of the element's own
|
|
20
|
+
* atoms is readable and is what this spec asserts. That is deliberate: the
|
|
21
|
+
* first version of this file matched atom NAMES (`/_o-0d0t5/`), the encoder
|
|
22
|
+
* emits `_o-0--5`, and the quiet test was red from the day it merged without
|
|
23
|
+
* anyone seeing it — CI runs `turbo run build` only (MPO-78). A declaration is
|
|
24
|
+
* the law; an atom name is an implementation detail of the encoder.
|
|
17
25
|
*/
|
|
18
26
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
19
27
|
import { cleanup } from "@testing-library/react";
|
|
@@ -29,6 +37,39 @@ function getSeparator(container: HTMLElement): HTMLElement {
|
|
|
29
37
|
return el as HTMLElement;
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
const escapeForRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Every value Tamagui declared for `property` through one of THIS element's
|
|
44
|
+
* atomic classes. Empty means the element does not carry that property at all.
|
|
45
|
+
*/
|
|
46
|
+
function declared(el: HTMLElement, property: string): string[] {
|
|
47
|
+
const atoms = el.className.split(/\s+/).filter(Boolean);
|
|
48
|
+
const values: string[] = [];
|
|
49
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
50
|
+
let rules: CSSRuleList | undefined;
|
|
51
|
+
try {
|
|
52
|
+
rules = sheet.cssRules;
|
|
53
|
+
} catch {
|
|
54
|
+
continue; // cross-origin sheet; nothing of ours lives there
|
|
55
|
+
}
|
|
56
|
+
for (const rule of Array.from(rules ?? [])) {
|
|
57
|
+
const parsed = (rule.cssText ?? "").match(/^([^{]+)\{([^}]*)\}$/);
|
|
58
|
+
if (!parsed) continue;
|
|
59
|
+
const [, selector, body] = parsed;
|
|
60
|
+
const mine = atoms.some((atom) =>
|
|
61
|
+
new RegExp(`\\.${escapeForRegExp(atom)}(?![\\w-])`).test(selector),
|
|
62
|
+
);
|
|
63
|
+
if (!mine) continue;
|
|
64
|
+
const decl = body.match(
|
|
65
|
+
new RegExp(`(?:^|[;\\s])${escapeForRegExp(property)}\\s*:\\s*([^;]+)`),
|
|
66
|
+
);
|
|
67
|
+
if (decl) values.push(decl[1].trim());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
|
|
32
73
|
/** Resolve a theme colour Variable to its raw value string. */
|
|
33
74
|
function themeColorVal(scheme: "light" | "dark", key: string): string {
|
|
34
75
|
const themes = getConfig().themes as Record<string, Record<string, { val?: unknown }>>;
|
|
@@ -47,14 +88,10 @@ describe("Separator — painted colour (the regression this component exists for
|
|
|
47
88
|
it("paints its 1px box with the $borderColor token, not a transparent box", () => {
|
|
48
89
|
const { container } = renderWithProviders(<Separator />);
|
|
49
90
|
const separator = getSeparator(container);
|
|
50
|
-
// PAINT: the background
|
|
51
|
-
expect(separator
|
|
52
|
-
// Nothing re-zeroes the paint: no transparent background atom.
|
|
53
|
-
expect(separator.className).not.toMatch(/_bg-transparent/);
|
|
91
|
+
// PAINT: the declared background is the borderColor token, not transparent.
|
|
92
|
+
expect(declared(separator, "background-color")).toEqual(["var(--borderColor)"]);
|
|
54
93
|
// GEOMETRY: exactly 1 CSS px tall — never 0.5 (§7.3: "1px is the value").
|
|
55
|
-
expect(separator
|
|
56
|
-
expect(separator.className).not.toMatch(/_h-0d0t5px/);
|
|
57
|
-
expect(separator.className).not.toMatch(/_mah-0d0t5px/);
|
|
94
|
+
expect(declared(separator, "height")).toEqual(["1px"]);
|
|
58
95
|
});
|
|
59
96
|
|
|
60
97
|
it("carries a visible $borderColor in BOTH schemes (token values are real colours)", () => {
|
|
@@ -69,15 +106,15 @@ describe("Separator — painted colour (the regression this component exists for
|
|
|
69
106
|
}
|
|
70
107
|
});
|
|
71
108
|
|
|
72
|
-
it("paints the same token
|
|
109
|
+
it("paints the same token under an explicit dark theme subtree", () => {
|
|
73
110
|
const { container } = renderWithProviders(
|
|
74
111
|
<Theme name="dark">
|
|
75
112
|
<Separator />
|
|
76
113
|
</Theme>,
|
|
77
114
|
);
|
|
78
115
|
const separator = getSeparator(container);
|
|
79
|
-
expect(separator
|
|
80
|
-
expect(separator
|
|
116
|
+
expect(declared(separator, "background-color")).toEqual(["var(--borderColor)"]);
|
|
117
|
+
expect(declared(separator, "height")).toEqual(["1px"]);
|
|
81
118
|
});
|
|
82
119
|
});
|
|
83
120
|
|
|
@@ -85,14 +122,14 @@ describe("Separator — quiet variant (§7.3: opacity 0.5, never a lighter colou
|
|
|
85
122
|
it("dims with opacity 0.5 and keeps the exact same paint token", () => {
|
|
86
123
|
const { container } = renderWithProviders(<Separator quiet />);
|
|
87
124
|
const separator = getSeparator(container);
|
|
88
|
-
expect(separator
|
|
89
|
-
// Never a lighter colour: the
|
|
90
|
-
expect(separator
|
|
125
|
+
expect(declared(separator, "opacity")).toEqual(["0.5"]);
|
|
126
|
+
// Never a lighter colour: the paint token is unchanged from the default.
|
|
127
|
+
expect(declared(separator, "background-color")).toEqual(["var(--borderColor)"]);
|
|
91
128
|
});
|
|
92
129
|
|
|
93
130
|
it("default separator carries no opacity dim", () => {
|
|
94
131
|
const { container } = renderWithProviders(<Separator />);
|
|
95
|
-
expect(getSeparator(container)
|
|
132
|
+
expect(declared(getSeparator(container), "opacity")).toEqual([]);
|
|
96
133
|
});
|
|
97
134
|
});
|
|
98
135
|
|
|
@@ -100,8 +137,8 @@ describe("Separator — vertical (§7.3: vertical separators are 1px wide)", ()
|
|
|
100
137
|
it("is 1px wide, releases the horizontal height, and declares its orientation", () => {
|
|
101
138
|
const { container } = renderWithProviders(<Separator vertical />);
|
|
102
139
|
const separator = getSeparator(container);
|
|
103
|
-
expect(separator
|
|
104
|
-
expect(separator
|
|
140
|
+
expect(declared(separator, "width")).toEqual(["1px"]);
|
|
141
|
+
expect(declared(separator, "height")).toEqual(["auto"]);
|
|
105
142
|
expect(separator.getAttribute("aria-orientation")).toBe("vertical");
|
|
106
143
|
});
|
|
107
144
|
|
|
@@ -115,10 +152,10 @@ describe("Separator — DG-OVF-07: never leftover space", () => {
|
|
|
115
152
|
it("cannot grow or shrink (flex: none discipline on the painting edge)", () => {
|
|
116
153
|
const { container } = renderWithProviders(<Separator />);
|
|
117
154
|
const separator = getSeparator(container);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
expect(separator
|
|
155
|
+
// The raw primitive it replaced declares `flex: 1` — the defect DG-OVF-07
|
|
156
|
+
// measures — so both ends of the flex story are pinned here.
|
|
157
|
+
expect(declared(separator, "flex-grow")).toEqual(["0"]);
|
|
158
|
+
expect(declared(separator, "flex-shrink")).toEqual(["0"]);
|
|
122
159
|
});
|
|
123
160
|
});
|
|
124
161
|
|
|
@@ -126,7 +163,6 @@ describe("Separator — consumer eject stays the last word (STD-EJECT-LAST)", ()
|
|
|
126
163
|
it("an explicit backgroundColor prop wins over the house paint", () => {
|
|
127
164
|
const { container } = renderWithProviders(<Separator backgroundColor="$red9" />);
|
|
128
165
|
const separator = getSeparator(container);
|
|
129
|
-
expect(separator
|
|
130
|
-
expect(separator.className).not.toMatch(/_bg-borderColo/);
|
|
166
|
+
expect(declared(separator, "background-color")).toEqual(["var(--red9)"]);
|
|
131
167
|
});
|
|
132
168
|
});
|
package/src/Separator/index.tsx
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* House Separator —
|
|
2
|
+
* House Separator — the catalog rule (MPO-47).
|
|
3
3
|
*
|
|
4
4
|
* The raw tamagui Separator rode the curated re-export as a "layout primitive
|
|
5
|
-
* with no house replacement"
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* with no house replacement". Two measured reasons it may not:
|
|
6
|
+
*
|
|
7
|
+
* - LC-45 / LC-70: a bare name a house barrel exports is a catalog part, and
|
|
8
|
+
* this one resolved to a raw primitive.
|
|
9
|
+
* - DG-OVF-07: the raw primitive declares `flex: 1`
|
|
10
|
+
* (@tamagui/separator@2.7.6 Separator.tsx), so the rule is CAPABLE of
|
|
11
|
+
* absorbing free space — the exact defect DG-OVF-07 names.
|
|
12
|
+
*
|
|
13
|
+
* The paint recipe is lifted from DropdownMenu's proven private
|
|
14
|
+
* SeparatorLine: a FILLED line rather than a border, because Blink floors
|
|
15
|
+
* sub-1px border widths and a filled fractional height is crisp in every
|
|
16
|
+
* engine.
|
|
17
|
+
*
|
|
18
|
+
* MEASUREMENT CORRECTION (2026-09-01). MPO-47's headline — "96 of 96
|
|
19
|
+
* separators paint nothing: height 1px, background rgba(0,0,0,0),
|
|
20
|
+
* border-top-width 0px" — was a PROBE ARTEFACT, not a defect. The gallery
|
|
21
|
+
* probe computed `paints = backgroundColor.alpha > 0 || borderTopWidth > 0`,
|
|
22
|
+
* while tamagui's Separator paints with borderBOTTOMWidth (horizontal) and
|
|
23
|
+
* borderRIGHTWidth (vertical), so the expression was structurally blind to
|
|
24
|
+
* the one primitive it audited. The shots that reading produced carry the
|
|
25
|
+
* painted 1px $borderColor rules in their own pixels, in both schemes; a
|
|
26
|
+
* pixel audit of all 52 action-and-feedback shots finds at least as many
|
|
27
|
+
* painted 1px $borderColor rules as the sidecar called blank, on every
|
|
28
|
+
* board. The raw rule did paint. It was still the wrong thing behind a
|
|
29
|
+
* catalog name, and it could still flex-grow — which is why this component
|
|
30
|
+
* stands.
|
|
13
31
|
*
|
|
14
32
|
* Measured law (tamagui-rendered-reference.md §7.3):
|
|
15
33
|
* - 1 CSS px, never 0.5px. Vertical separators are 1px wide.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* House Text knob wiring, NATIVE arm (MPO-20). Sibling of
|
|
3
|
+
* ./Heading.native.spec.tsx — see its header for WHY the arm is a separate
|
|
4
|
+
* file (the `vi.resetModules()` version returned a split module graph and
|
|
5
|
+
* proved nothing) and for what a `.native` spec can and cannot claim.
|
|
6
|
+
*/
|
|
7
|
+
import { cleanup, screen } from "@testing-library/react";
|
|
8
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
9
|
+
import { Preset } from "@multiplatform.one/theme";
|
|
10
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { Paragraph, SizableText, Text } from "./Text";
|
|
12
|
+
|
|
13
|
+
vi.mock("@multiplatform.one/platform", async (importOriginal) => ({
|
|
14
|
+
...(await importOriginal<typeof import("@multiplatform.one/platform")>()),
|
|
15
|
+
isWeb: false,
|
|
16
|
+
isNative: true,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
afterEach(cleanup);
|
|
20
|
+
|
|
21
|
+
const LABEL = "Measure the text node";
|
|
22
|
+
|
|
23
|
+
function classesByPrefix(el: Element, prefixes: string[]): string[] {
|
|
24
|
+
return String((el as HTMLElement).className || "")
|
|
25
|
+
.split(" ")
|
|
26
|
+
.filter((c) => prefixes.some((p) => c.startsWith(p)))
|
|
27
|
+
.sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Tamagui font-scope class on the TEXT NODE (`font_body` / `font_mono` / …). */
|
|
31
|
+
function fontScopeClass(el: Element): string {
|
|
32
|
+
return (
|
|
33
|
+
String((el as HTMLElement).className || "")
|
|
34
|
+
.split(" ")
|
|
35
|
+
.find((c) => c.startsWith("font_")) ?? ""
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Render, read the TEXT NODE's prefixed classes, clean up. */
|
|
40
|
+
function renderTextClasses(node: React.ReactElement, prefixes: string[]): string[] {
|
|
41
|
+
renderWithProviders(node);
|
|
42
|
+
const textNode = screen.getByText(LABEL);
|
|
43
|
+
const classes = classesByPrefix(textNode, prefixes);
|
|
44
|
+
cleanup();
|
|
45
|
+
return classes;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("house Text — NATIVE environment (isWeb false)", () => {
|
|
49
|
+
it("really is running with the native platform flags", async () => {
|
|
50
|
+
const platform = await import("@multiplatform.one/platform");
|
|
51
|
+
expect(platform.isWeb).toBe(false);
|
|
52
|
+
expect(platform.isNative).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("changes colour on a textAccent flip", () => {
|
|
56
|
+
const high = renderTextClasses(
|
|
57
|
+
<Preset overrides={{ textAccent: "high" }}>
|
|
58
|
+
<Text>{LABEL}</Text>
|
|
59
|
+
</Preset>,
|
|
60
|
+
["_col-"],
|
|
61
|
+
);
|
|
62
|
+
const low = renderTextClasses(
|
|
63
|
+
<Preset overrides={{ textAccent: "low" }}>
|
|
64
|
+
<Text>{LABEL}</Text>
|
|
65
|
+
</Preset>,
|
|
66
|
+
["_col-"],
|
|
67
|
+
);
|
|
68
|
+
expect(high).not.toHaveLength(0);
|
|
69
|
+
expect(high).not.toEqual(low);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("changes weight on a fontWeight flip, pinned to the 400/700 label law", () => {
|
|
73
|
+
const regular = renderTextClasses(
|
|
74
|
+
<Preset overrides={{ fontWeight: "regular" }}>
|
|
75
|
+
<Text>{LABEL}</Text>
|
|
76
|
+
</Preset>,
|
|
77
|
+
["_fow-"],
|
|
78
|
+
);
|
|
79
|
+
const bold = renderTextClasses(
|
|
80
|
+
<Preset overrides={{ fontWeight: "bold" }}>
|
|
81
|
+
<Text>{LABEL}</Text>
|
|
82
|
+
</Preset>,
|
|
83
|
+
["_fow-"],
|
|
84
|
+
);
|
|
85
|
+
const explicit400 = renderTextClasses(<Text fontWeight="400">{LABEL}</Text>, ["_fow-"]);
|
|
86
|
+
const explicit700 = renderTextClasses(<Text fontWeight="700">{LABEL}</Text>, ["_fow-"]);
|
|
87
|
+
expect(regular).not.toHaveLength(0);
|
|
88
|
+
expect(regular).not.toEqual(bold);
|
|
89
|
+
expect(regular).toEqual(explicit400);
|
|
90
|
+
expect(bold).toEqual(explicit700);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("changes family on a bodyFont flip — the props channel, not the CSS rescue", () => {
|
|
94
|
+
const scope = (node: React.ReactElement) => {
|
|
95
|
+
renderWithProviders(node);
|
|
96
|
+
const cls = fontScopeClass(screen.getByText(LABEL));
|
|
97
|
+
cleanup();
|
|
98
|
+
return cls;
|
|
99
|
+
};
|
|
100
|
+
const sans = scope(
|
|
101
|
+
<Preset overrides={{ bodyFont: "sans-serif" }}>
|
|
102
|
+
<Text>{LABEL}</Text>
|
|
103
|
+
</Preset>,
|
|
104
|
+
);
|
|
105
|
+
const mono = scope(
|
|
106
|
+
<Preset overrides={{ bodyFont: "mono" }}>
|
|
107
|
+
<Text>{LABEL}</Text>
|
|
108
|
+
</Preset>,
|
|
109
|
+
);
|
|
110
|
+
expect(sans).not.toBe("");
|
|
111
|
+
expect(sans).not.toBe(mono);
|
|
112
|
+
expect(mono).toBe("font_mono");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("carries the same knobs on the sibling body voices", () => {
|
|
116
|
+
for (const Voice of [SizableText, Paragraph]) {
|
|
117
|
+
const high = renderTextClasses(
|
|
118
|
+
<Preset overrides={{ textAccent: "high" }}>
|
|
119
|
+
<Voice>{LABEL}</Voice>
|
|
120
|
+
</Preset>,
|
|
121
|
+
["_col-"],
|
|
122
|
+
);
|
|
123
|
+
const low = renderTextClasses(
|
|
124
|
+
<Preset overrides={{ textAccent: "low" }}>
|
|
125
|
+
<Voice>{LABEL}</Voice>
|
|
126
|
+
</Preset>,
|
|
127
|
+
["_col-"],
|
|
128
|
+
);
|
|
129
|
+
expect(high).not.toHaveLength(0);
|
|
130
|
+
expect(high).not.toEqual(low);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("explicit consumer props still eject (STD-EJECT-LAST)", () => {
|
|
135
|
+
const ejected = renderTextClasses(
|
|
136
|
+
<Preset overrides={{ fontWeight: "bold" }}>
|
|
137
|
+
<Text fontWeight="400">{LABEL}</Text>
|
|
138
|
+
</Preset>,
|
|
139
|
+
["_fow-"],
|
|
140
|
+
);
|
|
141
|
+
const explicit400 = renderTextClasses(<Text fontWeight="400">{LABEL}</Text>, ["_fow-"]);
|
|
142
|
+
expect(ejected).toEqual(explicit400);
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/Text.spec.tsx
CHANGED
|
@@ -12,19 +12,18 @@
|
|
|
12
12
|
* family. Between knob flips and against explicit-prop renders of the
|
|
13
13
|
* same value. Hashed class names are compared by identity only.
|
|
14
14
|
*
|
|
15
|
-
* WEB arm
|
|
16
|
-
*
|
|
17
|
-
* module graph
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* — the only channel native has.
|
|
15
|
+
* This file is the WEB arm (the default module graph, isWeb true). The NATIVE
|
|
16
|
+
* arm is ./Text.native.spec.tsx, a separate FILE so that a file-scoped
|
|
17
|
+
* `vi.mock` gives it one coherent module graph. FontKnobStyles' CSS-variable
|
|
18
|
+
* rescue is absent in BOTH arms (renderWithProviders mounts no theme
|
|
19
|
+
* provider), so a flip that reaches the node proves the PROPS channel — the
|
|
20
|
+
* only channel native has.
|
|
22
21
|
*/
|
|
23
22
|
import { cleanup, screen } from "@testing-library/react";
|
|
24
23
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
25
24
|
import { Preset } from "@multiplatform.one/theme";
|
|
26
25
|
import { XStack } from "tamagui";
|
|
27
|
-
import { afterEach, describe, expect, it
|
|
26
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
28
27
|
import { Text } from "./Text";
|
|
29
28
|
|
|
30
29
|
afterEach(cleanup);
|
|
@@ -182,65 +181,8 @@ describe("house Text — fontWeight and bodyFont reach the text node", () => {
|
|
|
182
181
|
});
|
|
183
182
|
});
|
|
184
183
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
isWeb: false,
|
|
191
|
-
isNative: true,
|
|
192
|
-
}));
|
|
193
|
-
try {
|
|
194
|
-
// Everything must come from the SAME fresh graph: a Preset from the
|
|
195
|
-
// old graph would provide a context the fresh Text never reads.
|
|
196
|
-
const [{ Text: NativeText }, { Preset: NativePreset }, testUtils, testingLibrary] =
|
|
197
|
-
await Promise.all([
|
|
198
|
-
import("./Text"),
|
|
199
|
-
import("@multiplatform.one/theme"),
|
|
200
|
-
import("@multiplatform.one/test-utils"),
|
|
201
|
-
import("@testing-library/react"),
|
|
202
|
-
]);
|
|
203
|
-
|
|
204
|
-
const renderNative = (node: React.ReactElement, prefixes: string[]): string[] => {
|
|
205
|
-
testUtils.renderWithProviders(node);
|
|
206
|
-
const el = testingLibrary.screen.getByText(LABEL);
|
|
207
|
-
const classes = classesByPrefix(el, prefixes);
|
|
208
|
-
testingLibrary.cleanup();
|
|
209
|
-
return classes;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const high = renderNative(
|
|
213
|
-
<NativePreset overrides={{ textAccent: "high" }}>
|
|
214
|
-
<NativeText>{LABEL}</NativeText>
|
|
215
|
-
</NativePreset>,
|
|
216
|
-
["_col-"],
|
|
217
|
-
);
|
|
218
|
-
const low = renderNative(
|
|
219
|
-
<NativePreset overrides={{ textAccent: "low" }}>
|
|
220
|
-
<NativeText>{LABEL}</NativeText>
|
|
221
|
-
</NativePreset>,
|
|
222
|
-
["_col-"],
|
|
223
|
-
);
|
|
224
|
-
expect(high).not.toHaveLength(0);
|
|
225
|
-
expect(high).not.toEqual(low);
|
|
226
|
-
|
|
227
|
-
const regular = renderNative(
|
|
228
|
-
<NativePreset overrides={{ fontWeight: "regular" }}>
|
|
229
|
-
<NativeText>{LABEL}</NativeText>
|
|
230
|
-
</NativePreset>,
|
|
231
|
-
["_fow-"],
|
|
232
|
-
);
|
|
233
|
-
const bold = renderNative(
|
|
234
|
-
<NativePreset overrides={{ fontWeight: "bold" }}>
|
|
235
|
-
<NativeText>{LABEL}</NativeText>
|
|
236
|
-
</NativePreset>,
|
|
237
|
-
["_fow-"],
|
|
238
|
-
);
|
|
239
|
-
expect(regular).not.toHaveLength(0);
|
|
240
|
-
expect(regular).not.toEqual(bold);
|
|
241
|
-
} finally {
|
|
242
|
-
vi.doUnmock("@multiplatform.one/platform");
|
|
243
|
-
vi.resetModules();
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
184
|
+
// The NATIVE arm lives in ./Text.native.spec.tsx. It used to sit here as a
|
|
185
|
+
// `vi.resetModules()` + dynamic-import block, but that returned a SPLIT module
|
|
186
|
+
// graph — a fresh `renderWithProviders` over a stale `useResolvedKnobs` — and
|
|
187
|
+
// threw "Missing theme." instead of measuring anything. A file-scoped
|
|
188
|
+
// `vi.mock` in its own spec file is one coherent graph.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPO-38 AC-7, the source half: Tooltip owns no gap constant of its own.
|
|
3
|
+
*
|
|
4
|
+
* The runtime value is asserted where it lives — `layoutTokens.spec.ts` pins
|
|
5
|
+
* `OVERLAY_ANCHOR_GAP` at 4. Asserting that number again from a Tooltip spec
|
|
6
|
+
* proves nothing about Tooltip: it passes just as happily if this component
|
|
7
|
+
* goes back to a hardcoded `offset={4}`, which is exactly the drift the AC
|
|
8
|
+
* was written to close.
|
|
9
|
+
*
|
|
10
|
+
* So this is the source half, in the shape of `panelWidthContract.spec.ts`.
|
|
11
|
+
* A Tooltip is a FREE overlay — no trigger to attach to — so it is one of the
|
|
12
|
+
* few overlays `OVERLAY_ANCHOR_GAP` still governs after the cover ruling
|
|
13
|
+
* (`OVERLAY_ATTACH_GAP = 0`). It must READ that token rather than restate its
|
|
14
|
+
* value under a second name (LC-16 ONE-IMPLEMENTATION).
|
|
15
|
+
*
|
|
16
|
+
* Both twins are checked. The web/native split is where a duplicate constant
|
|
17
|
+
* survives longest: fixing one file and leaving the other still renders the
|
|
18
|
+
* right gap on the platform anyone happened to look at.
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { dirname, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { describe, expect, it } from "vitest";
|
|
24
|
+
|
|
25
|
+
const tooltipDir = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
/** The web twin, the native twin, and the module they share. */
|
|
28
|
+
const tooltipSources = ["index.tsx", "index.native.tsx", "tooltipShared.tsx"];
|
|
29
|
+
|
|
30
|
+
function read(relative: string): string {
|
|
31
|
+
return readFileSync(resolve(tooltipDir, relative), "utf8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("Tooltip gap contract (MPO-38 AC-7)", () => {
|
|
35
|
+
it.each(tooltipSources)("%s declares no gap constant of its own", (relative) => {
|
|
36
|
+
// Named for the constant this AC deleted, but the assertion is the rule,
|
|
37
|
+
// not the name: any local OFFSET/GAP constant is the same drift again.
|
|
38
|
+
expect(read(relative)).not.toMatch(/\b(?:const|let|var)\s+\w*(?:OFFSET|GAP)\w*\s*=/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it.each(["index.tsx", "index.native.tsx"])(
|
|
42
|
+
"%s positions the bubble with the shared FREE-overlay token",
|
|
43
|
+
(relative) => {
|
|
44
|
+
const source = read(relative);
|
|
45
|
+
expect(source).toMatch(
|
|
46
|
+
/import\s*\{[^}]*\bOVERLAY_ANCHOR_GAP\b[^}]*\}\s*from\s*"@multiplatform\.one\/theme"/s,
|
|
47
|
+
);
|
|
48
|
+
expect(source).toContain("offset={OVERLAY_ANCHOR_GAP}");
|
|
49
|
+
// A literal offset is the regression: it renders the same 4px today and
|
|
50
|
+
// silently stops tracking the token the moment the house rule moves.
|
|
51
|
+
expect(source).not.toMatch(/offset=\{\s*\d/);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Form, Input, Switch, useFormContext } from "@multiplatform.one/forms";
|
|
2
2
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
3
|
+
import { __resetDevWarnSeen } from "@multiplatform.one/theme";
|
|
3
4
|
import { act, fireEvent } from "@testing-library/react";
|
|
4
5
|
import { useEffect } from "react";
|
|
5
|
-
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
7
|
import { Wizard, useWizard, type WizardFormLike, type WizardStep } from "./index";
|
|
7
8
|
|
|
8
9
|
const STEPS: WizardStep[] = [
|
|
@@ -214,6 +215,27 @@ describe("Wizard LC-63 / DG-VAL", () => {
|
|
|
214
215
|
});
|
|
215
216
|
});
|
|
216
217
|
|
|
218
|
+
describe("Wizard LC-57 guardrail-clean at rest", () => {
|
|
219
|
+
it("a ready wizard at rest emits zero [theme] guardrail warnings", async () => {
|
|
220
|
+
__resetDevWarnSeen();
|
|
221
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
222
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
223
|
+
try {
|
|
224
|
+
renderWizard();
|
|
225
|
+
await act(async () => {
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
227
|
+
});
|
|
228
|
+
const themeWarns = [...warn.mock.calls, ...error.mock.calls]
|
|
229
|
+
.map((args) => String(args[0]))
|
|
230
|
+
.filter((msg) => msg.startsWith("[theme]"));
|
|
231
|
+
expect(themeWarns).toEqual([]);
|
|
232
|
+
} finally {
|
|
233
|
+
warn.mockRestore();
|
|
234
|
+
error.mockRestore();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
217
239
|
describe("Wizard states", () => {
|
|
218
240
|
it("draws loading, empty, and error shells", () => {
|
|
219
241
|
const loading = renderWithProviders(
|
package/src/Wizard/index.tsx
CHANGED
|
@@ -521,17 +521,10 @@ function WizardRoot({
|
|
|
521
521
|
{...props}
|
|
522
522
|
>
|
|
523
523
|
<YStack data-wizard-rail="" width="100%">
|
|
524
|
-
{/*
|
|
525
|
-
* MPO-81: no `labels` prop here. ProgressStepsProps does not declare
|
|
526
|
-
* one and ProgressSteps reads no such prop, so passing it was inert
|
|
527
|
-
* at runtime and failed the build with TS2322. `hideLabels` still
|
|
528
|
-
* drives the per-step caption below, which is where it has always
|
|
529
|
-
* had a real effect. Re-add the pass in the same change that gives
|
|
530
|
-
* ProgressSteps a labels prop it actually honours.
|
|
531
|
-
*/}
|
|
532
524
|
<ProgressSteps
|
|
533
525
|
steps={railSteps}
|
|
534
526
|
current={current}
|
|
527
|
+
labels={hideLabels ? "hidden" : "visible"}
|
|
535
528
|
compact={compact}
|
|
536
529
|
linear
|
|
537
530
|
onStepChange={(index) => {
|
|
@@ -85,19 +85,19 @@ function greet() {
|
|
|
85
85
|
describe("CodeBlock", () => {
|
|
86
86
|
it("keeps the copy action reachable at the 44px press floor", () => {
|
|
87
87
|
renderWithProviders(<CodeBlock>const x = 1;</CodeBlock>);
|
|
88
|
-
const button = screen.getByLabelText("Copy
|
|
88
|
+
const button = screen.getByLabelText("Copy to clipboard");
|
|
89
89
|
expect(button).toBeTruthy();
|
|
90
90
|
expect(button.getAttribute("data-press-min")).toBe(String(MIN_PRESS_TARGET));
|
|
91
|
+
// Default knob: the glyph-ring end-cap square itself meets the floor
|
|
92
|
+
// (44). Smaller size steps keep the floor by spilling hitSlop instead
|
|
93
|
+
// (CodeBlock.copy.spec covers the 36/52 cap geometry).
|
|
94
|
+
expect(Number(button.getAttribute("data-end-cap"))).toBeGreaterThanOrEqual(MIN_PRESS_TARGET);
|
|
91
95
|
const style = getComputedStyle(button);
|
|
92
96
|
expect(style.display).not.toBe("none");
|
|
93
|
-
const minWidth = Number.parseFloat(style.minWidth);
|
|
94
|
-
const minHeight = Number.parseFloat(style.minHeight);
|
|
95
|
-
if (!Number.isNaN(minWidth)) expect(minWidth).toBeGreaterThanOrEqual(MIN_PRESS_TARGET);
|
|
96
|
-
if (!Number.isNaN(minHeight)) expect(minHeight).toBeGreaterThanOrEqual(MIN_PRESS_TARGET);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
it("hides the copy action only when disableCopy is set", () => {
|
|
100
100
|
renderWithProviders(<CodeBlock disableCopy>const x = 1;</CodeBlock>);
|
|
101
|
-
expect(screen.queryByLabelText("Copy
|
|
101
|
+
expect(screen.queryByLabelText("Copy to clipboard")).toBeNull();
|
|
102
102
|
});
|
|
103
103
|
});
|