@bearmenu/ui 0.8.21 → 0.8.23
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/{chunk-DJD3EMPK.js → chunk-YE4ZQUTI.js} +1 -1
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/coach-marks.d.ts +43 -6
- package/dist/components/coach-marks.js +116 -30
- package/dist/components/command.d.ts +7 -7
- package/dist/components/concept-compare.d.ts +1 -1
- package/dist/components/doorway-tiles.d.ts +1 -1
- package/dist/components/grade-atom.d.ts +1 -1
- package/dist/components/grade-atom.js +1 -1
- package/dist/components/kitchen-dossier.d.ts +1 -1
- package/dist/components/menu-dead-end.d.ts +1 -1
- package/dist/components/menu-family-block.d.ts +1 -1
- package/dist/components/menu-family-card.d.ts +1 -1
- package/dist/components/menu-filter-chips.d.ts +1 -1
- package/dist/components/menu-group-rows.d.ts +1 -1
- package/dist/components/menu-index-header.d.ts +1 -1
- package/dist/components/menu-index-list.d.ts +1 -1
- package/dist/components/menu-intent-hero.d.ts +1 -1
- package/dist/components/menu-item.d.ts +1 -1
- package/dist/components/menu-kitchen-list.d.ts +1 -1
- package/dist/components/menu-mocks.d.ts +1 -1
- package/dist/components/menu-types.d.ts +1 -1
- package/dist/components/open-now-list.d.ts +1 -1
- package/dist/components/place-about-tab.d.ts +1 -1
- package/dist/components/place-card.d.ts +1 -1
- package/dist/components/place-card.js +1 -1
- package/dist/components/place-details-mobile.d.ts +1 -1
- package/dist/components/place-schedule-week.d.ts +1 -1
- package/dist/components/place-types.d.ts +1 -1
- package/dist/components/qa-block.d.ts +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/text.d.ts +3 -3
- package/dist/components/top-comparison.d.ts +1 -1
- package/dist/components/top-cross-link.d.ts +1 -1
- package/dist/components/top-ranking-entry.d.ts +1 -1
- package/dist/components/top-ranking-hero.d.ts +1 -1
- package/dist/components/top-types.d.ts +1 -1
- package/dist/components/venue-grade-dimensions.d.ts +1 -1
- package/dist/components/venue-quotes.d.ts +1 -1
- package/dist/components/venue-wall.d.ts +1 -1
- package/dist/{place-types-BHdeMMiG.d.ts → place-types-C4rR3LTs.d.ts} +7 -0
- package/package.json +1 -1
- package/src/components/coach-marks.stories.tsx +20 -4
- package/src/components/coach-marks.test.tsx +181 -6
- package/src/components/coach-marks.tsx +204 -45
- package/src/components/grade-atom.test.tsx +32 -0
- package/src/components/grade-atom.tsx +8 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OVERLAY_PILL } from '../chunk-EZG2NELX.js';
|
|
2
2
|
import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
|
|
3
3
|
import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
|
|
4
|
-
import { GradeAtom } from '../chunk-
|
|
4
|
+
import { GradeAtom } from '../chunk-YE4ZQUTI.js';
|
|
5
5
|
import { showsGradeAtom } from '../chunk-I5V446BX.js';
|
|
6
6
|
import { Separator } from '../chunk-NWZGSLPU.js';
|
|
7
7
|
import { springSnappy } from '../chunk-OIV7B44M.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { P as PlacePreview, e as PlaceDetailsMobileLabels } from '../place-types-
|
|
2
|
+
import { P as PlacePreview, e as PlaceDetailsMobileLabels } from '../place-types-C4rR3LTs.js';
|
|
3
3
|
import '../lib/grade.js';
|
|
4
4
|
|
|
5
5
|
interface PlaceDetailsMobileProps {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { f as PlaceSchedule, g as PlaceScheduleWeekLabels } from '../place-types-
|
|
2
|
+
import { f as PlaceSchedule, g as PlaceScheduleWeekLabels } from '../place-types-C4rR3LTs.js';
|
|
3
3
|
import '../lib/grade.js';
|
|
4
4
|
|
|
5
5
|
interface PlaceScheduleWeekProps {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { C as CitedText, k as CitedValue, M as MenuSizeAndPrice, b as PlaceAboutTabLabels, d as PlaceCardLabels, e as PlaceDetailsMobileLabels, l as PlaceGrade, a as PlaceGradeDimensions, m as PlaceHourBlock, c as PlaceLinkKind, n as PlaceMustTry, P as PlacePreview, o as PlacePriceRange, p as PlaceReviewsDigest, f as PlaceSchedule, g as PlaceScheduleWeekLabels, W as WeekdayKey } from '../place-types-
|
|
1
|
+
export { C as CitedText, k as CitedValue, M as MenuSizeAndPrice, b as PlaceAboutTabLabels, d as PlaceCardLabels, e as PlaceDetailsMobileLabels, l as PlaceGrade, a as PlaceGradeDimensions, m as PlaceHourBlock, c as PlaceLinkKind, n as PlaceMustTry, P as PlacePreview, o as PlacePriceRange, p as PlaceReviewsDigest, f as PlaceSchedule, g as PlaceScheduleWeekLabels, W as WeekdayKey } from '../place-types-C4rR3LTs.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import '../lib/grade.js';
|
|
@@ -4,11 +4,11 @@ import { VariantProps } from 'class-variance-authority';
|
|
|
4
4
|
|
|
5
5
|
declare const spinnerVariants: (props?: ({
|
|
6
6
|
size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
|
|
7
|
-
tone?: "
|
|
7
|
+
tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
declare const Spinner: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
|
|
10
10
|
size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
|
|
11
|
-
tone?: "
|
|
11
|
+
tone?: "default" | "destructive" | "inherit" | "primary" | null | undefined;
|
|
12
12
|
} & class_variance_authority_types.ClassProp) | undefined) => string> & {
|
|
13
13
|
/** Visually-hidden label for screen readers. Defaults to "Loading". */
|
|
14
14
|
label?: string;
|
package/dist/components/tag.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const tagVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "destructive" | "outline" | "
|
|
6
|
+
variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
|
|
7
7
|
size?: "default" | "sm" | "lg" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
declare const Tag: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
|
|
10
|
-
variant?: "default" | "destructive" | "outline" | "
|
|
10
|
+
variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
|
|
11
11
|
size?: "default" | "sm" | "lg" | null | undefined;
|
|
12
12
|
} & class_variance_authority_types.ClassProp) | undefined) => string> & {
|
|
13
13
|
onRemove?: () => void;
|
|
@@ -22,9 +22,9 @@ import { VariantProps } from 'class-variance-authority';
|
|
|
22
22
|
* Weights are 400 / 600 / 700. 500 is not in the system.
|
|
23
23
|
*/
|
|
24
24
|
declare const textVariants: (props?: ({
|
|
25
|
-
variant?: "
|
|
26
|
-
color?: "
|
|
27
|
-
weight?: "
|
|
25
|
+
variant?: "small" | "display" | "h2" | "h3" | "label" | "body" | "caption" | "h1" | "h4" | "body-lg" | "body-sm" | "overline" | null | undefined;
|
|
26
|
+
color?: "default" | "destructive" | "inherit" | "muted" | "primary" | "primary-foreground" | null | undefined;
|
|
27
|
+
weight?: "medium" | "bold" | "normal" | "semibold" | null | undefined;
|
|
28
28
|
leading?: "none" | "normal" | "tight" | "snug" | "relaxed" | "loose" | null | undefined;
|
|
29
29
|
tracking?: "normal" | "tight" | "wide" | "wider" | null | undefined;
|
|
30
30
|
wrap?: "balance" | "pretty" | "nowrap" | null | undefined;
|
|
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import { MenuLinkComponent } from './menu-types.js';
|
|
3
3
|
import { TopComparisonRow } from './top-types.js';
|
|
4
4
|
import 'react';
|
|
5
|
-
import '../place-types-
|
|
5
|
+
import '../place-types-C4rR3LTs.js';
|
|
6
6
|
import '../lib/grade.js';
|
|
7
7
|
|
|
8
8
|
type TopComparisonLabels = {
|
|
@@ -3,7 +3,7 @@ import { MenuItemLabels } from './menu-item.js';
|
|
|
3
3
|
import { MenuItemPreview, MenuLinkComponent } from './menu-types.js';
|
|
4
4
|
import { TopRankEntry } from './top-types.js';
|
|
5
5
|
import 'react';
|
|
6
|
-
import '../place-types-
|
|
6
|
+
import '../place-types-C4rR3LTs.js';
|
|
7
7
|
import '../lib/grade.js';
|
|
8
8
|
|
|
9
9
|
type TopRankingEntryLabels = {
|
|
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import { MenuLinkComponent } from './menu-types.js';
|
|
3
3
|
import { TopExit } from './top-types.js';
|
|
4
4
|
import 'react';
|
|
5
|
-
import '../place-types-
|
|
5
|
+
import '../place-types-C4rR3LTs.js';
|
|
6
6
|
import '../lib/grade.js';
|
|
7
7
|
|
|
8
8
|
type TopRankingHeroProps = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
|
-
import { P as PlacePreview } from '../place-types-
|
|
3
|
+
import { P as PlacePreview } from '../place-types-C4rR3LTs.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import '../lib/grade.js';
|
|
6
6
|
|
|
@@ -12,6 +12,13 @@ type GradeAtomLabels = {
|
|
|
12
12
|
aria: (value: string) => string;
|
|
13
13
|
/** Title + aria suffix when `grade.capped`, e.g. "Capped for low confidence". */
|
|
14
14
|
capped: string;
|
|
15
|
+
/**
|
|
16
|
+
* The number as the locale writes it, e.g. `8,7` in Romanian. Receives the
|
|
17
|
+
* grade already rounded to one decimal; return the glyphs to print. Defaults
|
|
18
|
+
* to `formatGrade` (`8.7`, a trailing `.0` dropped). It is a label because the
|
|
19
|
+
* decimal mark is translation, not styling — and `aria` gets the same string.
|
|
20
|
+
*/
|
|
21
|
+
value?: (overall: number) => string;
|
|
15
22
|
};
|
|
16
23
|
type GradeAtomProps = {
|
|
17
24
|
grade: PlaceGrade;
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ const meta: Meta<typeof CoachMarks> = {
|
|
|
12
12
|
docs: {
|
|
13
13
|
description: {
|
|
14
14
|
component:
|
|
15
|
-
"A guided tour drawn over the real page: the page dims, the current step's target is cut out in a spotlight with its step number on the corner, one callout says what the target is. Targets are `data-coach-mark` attributes on the page's own elements.",
|
|
15
|
+
"A guided tour drawn over the real page: the page dims, the current step's target is cut out in a spotlight with its step number on the corner, one callout says what the target is. Targets are `data-coach-mark` attributes on the page's own elements. The spotlight is a hole in the dimming: a tap on the ringed control reaches it and ends the tour. A `region` stop (a list, a rail) is cut out without the ring; a pinned control passes `scroll: false`; `inset` names the page's fixed chrome.",
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
},
|
|
@@ -37,14 +37,23 @@ const STOPS: CoachMarkStop[] = [
|
|
|
37
37
|
title: "Cards, or rows",
|
|
38
38
|
body: "The same menu items either way: rows fit three times as many.",
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
id: "results",
|
|
42
|
+
kind: "region",
|
|
43
|
+
title: "What matched",
|
|
44
|
+
body: "A region is cut out without the ring; tapping a row inside it navigates, and the tour steps out of the way.",
|
|
45
|
+
},
|
|
40
46
|
];
|
|
41
47
|
|
|
42
48
|
function Demo() {
|
|
43
49
|
const [open, setOpen] = useState(true);
|
|
50
|
+
const [taps, setTaps] = useState(0);
|
|
44
51
|
return (
|
|
45
52
|
<div className="min-h-[720px] bg-background p-8 text-foreground">
|
|
46
53
|
<div className="flex items-center gap-3">
|
|
47
|
-
<Button data-coach-mark="search"
|
|
54
|
+
<Button data-coach-mark="search" onClick={() => setTaps((count) => count + 1)}>
|
|
55
|
+
Search and filter{taps > 0 ? ` · tapped ${taps}` : ""}
|
|
56
|
+
</Button>
|
|
48
57
|
<Button variant="outline" data-coach-mark="view">
|
|
49
58
|
Rows
|
|
50
59
|
</Button>
|
|
@@ -57,9 +66,16 @@ function Demo() {
|
|
|
57
66
|
<span>Centru ×</span>
|
|
58
67
|
</div>
|
|
59
68
|
<p className="mt-10 max-w-[60ch] text-sm text-muted-foreground">
|
|
60
|
-
The page beneath the tour. Nothing here re-renders; the overlay measures the
|
|
61
|
-
|
|
69
|
+
The page beneath the tour. Nothing here re-renders; the overlay measures the targets and
|
|
70
|
+
draws over them. Tap the spotlit button: the tap lands, and the tour ends.
|
|
62
71
|
</p>
|
|
72
|
+
<ul data-coach-mark="results" className="mt-6 max-w-md divide-y divide-border rounded-2xl border border-border">
|
|
73
|
+
{["Papanași", "Ciorbă de burtă", "Sarmale"].map((name) => (
|
|
74
|
+
<li key={name} className="px-4 py-3 text-sm">
|
|
75
|
+
{name}
|
|
76
|
+
</li>
|
|
77
|
+
))}
|
|
78
|
+
</ul>
|
|
63
79
|
<CoachMarks
|
|
64
80
|
open={open}
|
|
65
81
|
stops={STOPS}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
3
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
4
|
import { CoachMarks, type CoachMarkStop } from "./coach-marks";
|
|
4
5
|
|
|
@@ -55,8 +56,10 @@ function pins(): string[] {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
// jsdom has no layout: every rect is zero, which is enough to render. A
|
|
58
|
-
// target has boxes when it is in the document, none when it is not.
|
|
59
|
-
|
|
59
|
+
// target has boxes when it is in the document, none when it is not. The
|
|
60
|
+
// scroll mock sits on HTMLElement, where the test setup's stub is.
|
|
61
|
+
const scrollIntoView = vi.fn();
|
|
62
|
+
HTMLElement.prototype.scrollIntoView = scrollIntoView;
|
|
60
63
|
Element.prototype.getClientRects = function () {
|
|
61
64
|
return (this.isConnected ? [{}] : []) as unknown as DOMRectList;
|
|
62
65
|
};
|
|
@@ -121,13 +124,185 @@ describe("CoachMarks", () => {
|
|
|
121
124
|
expect(onClose).not.toHaveBeenCalled();
|
|
122
125
|
});
|
|
123
126
|
|
|
124
|
-
it("Skip
|
|
127
|
+
it("Skip and Escape close; the dimmed page does not", () => {
|
|
125
128
|
const onClose = vi.fn();
|
|
126
129
|
render(<Page onClose={onClose} />);
|
|
127
130
|
fireEvent.click(screen.getByText("Skip"));
|
|
128
131
|
fireEvent.keyDown(window, { key: "Escape" });
|
|
129
|
-
|
|
130
|
-
expect(
|
|
132
|
+
expect(onClose).toHaveBeenCalledTimes(2);
|
|
133
|
+
expect(screen.getAllByRole("button", { name: "Skip" })).toHaveLength(1);
|
|
134
|
+
const dim = screen.getByRole("dialog").firstElementChild;
|
|
135
|
+
fireEvent.click(dim as Element);
|
|
136
|
+
expect(onClose).toHaveBeenCalledTimes(2);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("a tap on the spotlit target reaches it and ends the tour", () => {
|
|
140
|
+
const onClose = vi.fn();
|
|
141
|
+
const onTarget = vi.fn();
|
|
142
|
+
render(
|
|
143
|
+
<>
|
|
144
|
+
<button type="button" data-coach-mark="a" onClick={onTarget}>
|
|
145
|
+
a
|
|
146
|
+
</button>
|
|
147
|
+
<button type="button" data-coach-mark="b">
|
|
148
|
+
b
|
|
149
|
+
</button>
|
|
150
|
+
<CoachMarks open stops={STOPS.slice(0, 2)} onClose={onClose} labels={labels} />
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
fireEvent.click(screen.getByRole("button", { name: "b" }));
|
|
154
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
155
|
+
fireEvent.click(screen.getByRole("button", { name: "a" }));
|
|
156
|
+
expect(onTarget).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("hears a tap on a target that stops propagation", () => {
|
|
161
|
+
const onClose = vi.fn();
|
|
162
|
+
render(
|
|
163
|
+
<>
|
|
164
|
+
<div data-coach-mark="a">
|
|
165
|
+
<button type="button" onClick={(event) => event.stopPropagation()}>
|
|
166
|
+
inner
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
<CoachMarks open stops={STOPS.slice(0, 1)} onClose={onClose} labels={labels} />
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
fireEvent.click(screen.getByRole("button", { name: "inner" }));
|
|
173
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("settles when the consumer rebuilds its stops and inset on every render", () => {
|
|
177
|
+
const onProgress = vi.fn();
|
|
178
|
+
function Consumer() {
|
|
179
|
+
const [progress, setProgress] = useState<string>("");
|
|
180
|
+
return (
|
|
181
|
+
<>
|
|
182
|
+
<button type="button" data-coach-mark="a">
|
|
183
|
+
a
|
|
184
|
+
</button>
|
|
185
|
+
<span data-testid="progress">{progress}</span>
|
|
186
|
+
<CoachMarks
|
|
187
|
+
open
|
|
188
|
+
stops={STOPS.slice(0, 1).map((stop) => ({ ...stop }))}
|
|
189
|
+
inset={{ top: 68 }}
|
|
190
|
+
onClose={vi.fn()}
|
|
191
|
+
onProgress={(step, total, stop) => {
|
|
192
|
+
onProgress(step, total, stop);
|
|
193
|
+
setProgress(`${step}/${total}`);
|
|
194
|
+
}}
|
|
195
|
+
labels={labels}
|
|
196
|
+
/>
|
|
197
|
+
</>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
render(<Consumer />);
|
|
201
|
+
expect(screen.getByTestId("progress")).toHaveTextContent("1/1");
|
|
202
|
+
expect(onProgress).toHaveBeenCalledTimes(1);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("a region is cut out without the control's ring", () => {
|
|
206
|
+
const { rerender } = render(<Page onClose={vi.fn()} />);
|
|
207
|
+
const spotlight = () => screen.getByRole("dialog").children[1];
|
|
208
|
+
expect(spotlight().className).toContain("ring-2");
|
|
209
|
+
rerender(<Page onClose={vi.fn()} stops={STOPS.map((stop) => ({ ...stop, kind: "region" }))} />);
|
|
210
|
+
expect(spotlight().className).not.toContain("ring-2");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("scrolling to the target", () => {
|
|
214
|
+
/** A page whose targets sit at `top`, `height` 40, in an 800px viewport. */
|
|
215
|
+
function placeTargets(top: number) {
|
|
216
|
+
vi.spyOn(window, "innerHeight", "get").mockReturnValue(800);
|
|
217
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({
|
|
218
|
+
top,
|
|
219
|
+
bottom: top + 40,
|
|
220
|
+
left: 0,
|
|
221
|
+
right: 100,
|
|
222
|
+
width: 100,
|
|
223
|
+
height: 40,
|
|
224
|
+
x: 0,
|
|
225
|
+
y: top,
|
|
226
|
+
toJSON: () => ({}),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
beforeEach(() => scrollIntoView.mockClear());
|
|
231
|
+
afterEach(() => vi.restoreAllMocks());
|
|
232
|
+
|
|
233
|
+
it("centres a target out of view", () => {
|
|
234
|
+
placeTargets(900);
|
|
235
|
+
render(<Page onClose={vi.fn()} />);
|
|
236
|
+
expect(scrollIntoView).toHaveBeenCalledWith(expect.objectContaining({ block: "center" }));
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("leaves the page where it is when the target is already in view", () => {
|
|
240
|
+
placeTargets(300);
|
|
241
|
+
render(<Page onClose={vi.fn()} />);
|
|
242
|
+
// Still a request — it aborts the previous stop's smooth scroll — but
|
|
243
|
+
// one that moves nothing.
|
|
244
|
+
expect(scrollIntoView).toHaveBeenCalledWith(expect.objectContaining({ block: "nearest" }));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("aligns a target taller than the band to its top, clear of the chrome", () => {
|
|
248
|
+
vi.spyOn(window, "innerHeight", "get").mockReturnValue(800);
|
|
249
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({
|
|
250
|
+
top: 900,
|
|
251
|
+
bottom: 2400,
|
|
252
|
+
left: 0,
|
|
253
|
+
right: 100,
|
|
254
|
+
width: 100,
|
|
255
|
+
height: 1500,
|
|
256
|
+
x: 0,
|
|
257
|
+
y: 900,
|
|
258
|
+
toJSON: () => ({}),
|
|
259
|
+
});
|
|
260
|
+
render(
|
|
261
|
+
<>
|
|
262
|
+
<button type="button" data-coach-mark="a">
|
|
263
|
+
a
|
|
264
|
+
</button>
|
|
265
|
+
<CoachMarks
|
|
266
|
+
open
|
|
267
|
+
stops={STOPS.slice(0, 1)}
|
|
268
|
+
onClose={vi.fn()}
|
|
269
|
+
labels={labels}
|
|
270
|
+
inset={{ top: 68 }}
|
|
271
|
+
/>
|
|
272
|
+
</>
|
|
273
|
+
);
|
|
274
|
+
expect(scrollIntoView).toHaveBeenCalledWith(expect.objectContaining({ block: "start" }));
|
|
275
|
+
expect(screen.getByRole("button", { name: "a" }).style.scrollMarginTop).toBe("76px");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("counts the fixed chrome as out of view", () => {
|
|
279
|
+
placeTargets(20);
|
|
280
|
+
const { unmount } = render(<Page onClose={vi.fn()} />);
|
|
281
|
+
expect(scrollIntoView).toHaveBeenCalledWith(expect.objectContaining({ block: "nearest" }));
|
|
282
|
+
unmount();
|
|
283
|
+
scrollIntoView.mockClear();
|
|
284
|
+
render(
|
|
285
|
+
<>
|
|
286
|
+
<button type="button" data-coach-mark="a">
|
|
287
|
+
a
|
|
288
|
+
</button>
|
|
289
|
+
<CoachMarks
|
|
290
|
+
open
|
|
291
|
+
stops={STOPS.slice(0, 1)}
|
|
292
|
+
onClose={vi.fn()}
|
|
293
|
+
labels={labels}
|
|
294
|
+
inset={{ top: 68 }}
|
|
295
|
+
/>
|
|
296
|
+
</>
|
|
297
|
+
);
|
|
298
|
+
expect(scrollIntoView).toHaveBeenCalledWith(expect.objectContaining({ block: "center" }));
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("never scrolls a stop that opted out — a pinned control", () => {
|
|
302
|
+
placeTargets(900);
|
|
303
|
+
render(<Page onClose={vi.fn()} stops={STOPS.map((stop) => ({ ...stop, scroll: false }))} />);
|
|
304
|
+
expect(scrollIntoView).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
131
306
|
});
|
|
132
307
|
|
|
133
308
|
it("closes at once when no stop has a target, and renders nothing while closed", () => {
|