@carto/ps-react-ui 5.0.0 → 5.2.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/actions-CF8RNKd6.js +1161 -0
- package/dist/actions-CF8RNKd6.js.map +1 -0
- package/dist/category-D01ruG-l.js +842 -0
- package/dist/category-D01ruG-l.js.map +1 -0
- package/dist/{download-DdaK3qgC.js → download-DGgFOaXj.js} +81 -80
- package/dist/download-DGgFOaXj.js.map +1 -0
- package/dist/{echart-DeBgdOtX.js → echart-CwzbPElc.js} +29 -29
- package/dist/echart-CwzbPElc.js.map +1 -0
- package/dist/{formula-noJzQwKo.js → formula-BIxJr1P3.js} +6 -6
- package/dist/{formula-noJzQwKo.js.map → formula-BIxJr1P3.js.map} +1 -1
- package/dist/{markdown-2dfL6Cmb.js → markdown-oo9mnJun.js} +6 -6
- package/dist/{markdown-2dfL6Cmb.js.map → markdown-oo9mnJun.js.map} +1 -1
- package/dist/{spread-Ccpg9fi0.js → spread-CgCTKPQD.js} +7 -7
- package/dist/{spread-Ccpg9fi0.js.map → spread-CgCTKPQD.js.map} +1 -1
- package/dist/{table-Bx9Uj3U0.js → table-lb5momun.js} +3 -3
- package/dist/{table-Bx9Uj3U0.js.map → table-lb5momun.js.map} +1 -1
- package/dist/types/widgets-v2/actions/download/csv-item.d.ts +3 -3
- package/dist/types/widgets-v2/actions/download/index.d.ts +1 -1
- package/dist/types/widgets-v2/actions/download/png-item.d.ts +2 -2
- package/dist/types/widgets-v2/actions/download/types.d.ts +12 -1
- package/dist/types/widgets-v2/actions/fullscreen/fullscreen.d.ts +42 -16
- package/dist/types/widgets-v2/actions/fullscreen/style.d.ts +9 -0
- package/dist/types/widgets-v2/actions/index.d.ts +1 -1
- package/dist/types/widgets-v2/bar/download.d.ts +3 -3
- package/dist/types/widgets-v2/category/category-ui.d.ts +9 -1
- package/dist/types/widgets-v2/category/download.d.ts +3 -3
- package/dist/types/widgets-v2/echart/echart-ui.d.ts +2 -2
- package/dist/types/widgets-v2/echart/echart.d.ts +9 -1
- package/dist/types/widgets-v2/echart/style.d.ts +2 -0
- package/dist/types/widgets-v2/formula/download.d.ts +3 -3
- package/dist/types/widgets-v2/histogram/download.d.ts +3 -3
- package/dist/types/widgets-v2/index.d.ts +2 -2
- package/dist/types/widgets-v2/markdown/download.d.ts +3 -3
- package/dist/types/widgets-v2/pie/download.d.ts +3 -3
- package/dist/types/widgets-v2/scatterplot/download.d.ts +3 -3
- package/dist/types/widgets-v2/spread/download.d.ts +3 -3
- package/dist/types/widgets-v2/table/download.d.ts +3 -3
- package/dist/types/widgets-v2/timeseries/download.d.ts +3 -3
- package/dist/types/widgets-v2/toolbox/toolbox.d.ts +1 -1
- package/dist/widgets-v2/actions.js +2 -2
- package/dist/widgets-v2/bar.js +7 -7
- package/dist/widgets-v2/bar.js.map +1 -1
- package/dist/widgets-v2/category.js +1 -1
- package/dist/widgets-v2/echart.js +1 -1
- package/dist/widgets-v2/formula.js +1 -1
- package/dist/widgets-v2/histogram.js +10 -10
- package/dist/widgets-v2/histogram.js.map +1 -1
- package/dist/widgets-v2/markdown.js +1 -1
- package/dist/widgets-v2/pie.js +10 -11
- package/dist/widgets-v2/pie.js.map +1 -1
- package/dist/widgets-v2/scatterplot.js +9 -9
- package/dist/widgets-v2/scatterplot.js.map +1 -1
- package/dist/widgets-v2/spread.js +1 -1
- package/dist/widgets-v2/table.js +1 -1
- package/dist/widgets-v2/timeseries.js +12 -12
- package/dist/widgets-v2/timeseries.js.map +1 -1
- package/dist/widgets-v2.js +274 -333
- package/dist/widgets-v2.js.map +1 -1
- package/package.json +1 -1
- package/src/widgets-v2/actions/download/csv-item.test.tsx +3 -3
- package/src/widgets-v2/actions/download/csv-item.tsx +6 -6
- package/src/widgets-v2/actions/download/download.tsx +5 -3
- package/src/widgets-v2/actions/download/index.ts +1 -1
- package/src/widgets-v2/actions/download/png-item.test.tsx +5 -3
- package/src/widgets-v2/actions/download/png-item.tsx +4 -4
- package/src/widgets-v2/actions/download/types.ts +15 -1
- package/src/widgets-v2/actions/fullscreen/fullscreen-store.test.tsx +155 -0
- package/src/widgets-v2/actions/fullscreen/fullscreen.test.tsx +93 -33
- package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +272 -86
- package/src/widgets-v2/actions/fullscreen/style.ts +31 -4
- package/src/widgets-v2/actions/index.ts +1 -0
- package/src/widgets-v2/actions/searcher/searcher.tsx +16 -0
- package/src/widgets-v2/bar/download.test.tsx +5 -3
- package/src/widgets-v2/bar/download.tsx +5 -4
- package/src/widgets-v2/category/category-ui.tsx +24 -13
- package/src/widgets-v2/category/category.tsx +37 -2
- package/src/widgets-v2/category/download.test.ts +4 -2
- package/src/widgets-v2/category/download.ts +5 -4
- package/src/widgets-v2/echart/echart-ui.test.tsx +6 -3
- package/src/widgets-v2/echart/echart-ui.tsx +6 -2
- package/src/widgets-v2/echart/echart.test.tsx +1 -1
- package/src/widgets-v2/echart/echart.tsx +10 -0
- package/src/widgets-v2/echart/style.ts +16 -1
- package/src/widgets-v2/formula/download.test.tsx +3 -1
- package/src/widgets-v2/formula/download.tsx +5 -4
- package/src/widgets-v2/histogram/download.test.ts +6 -4
- package/src/widgets-v2/histogram/download.ts +5 -4
- package/src/widgets-v2/index.ts +1 -0
- package/src/widgets-v2/markdown/download.test.ts +2 -2
- package/src/widgets-v2/markdown/download.tsx +5 -4
- package/src/widgets-v2/pie/download.test.ts +7 -3
- package/src/widgets-v2/pie/download.ts +5 -4
- package/src/widgets-v2/pie/options.test.ts +8 -0
- package/src/widgets-v2/pie/options.ts +3 -8
- package/src/widgets-v2/scatterplot/download.test.ts +4 -2
- package/src/widgets-v2/scatterplot/download.ts +5 -4
- package/src/widgets-v2/spread/download.test.ts +3 -1
- package/src/widgets-v2/spread/download.ts +5 -4
- package/src/widgets-v2/table/download.test.ts +5 -3
- package/src/widgets-v2/table/download.ts +4 -3
- package/src/widgets-v2/timeseries/download.test.ts +6 -4
- package/src/widgets-v2/timeseries/download.ts +5 -4
- package/src/widgets-v2/toolbox/toolbox.tsx +1 -1
- package/dist/actions-yPZwFxSZ.js +0 -1004
- package/dist/actions-yPZwFxSZ.js.map +0 -1
- package/dist/category-DGUDabsi.js +0 -829
- package/dist/category-DGUDabsi.js.map +0 -1
- package/dist/download-DdaK3qgC.js.map +0 -1
- package/dist/echart-DeBgdOtX.js.map +0 -1
|
@@ -11,5 +11,5 @@ export { CSVIcon, PNGIcon, MarkdownIcon } from './icons'
|
|
|
11
11
|
export { buildPngDownloadItem, type BuildPngDownloadItemArgs } from './png-item'
|
|
12
12
|
export { buildCsvDownloadItem, type BuildCsvDownloadItemArgs } from './csv-item'
|
|
13
13
|
export { DOWNLOAD_ITEM_IDS, type DownloadItemId } from './constants'
|
|
14
|
-
export type { DownloadItem } from './types'
|
|
14
|
+
export type { DownloadItem, DownloadContext } from './types'
|
|
15
15
|
export { DEFAULT_DOWNLOAD_LABELS, type DownloadLabels } from './labels'
|
|
@@ -44,7 +44,7 @@ describe('buildPngDownloadItem', () => {
|
|
|
44
44
|
filename: 'sales',
|
|
45
45
|
getCaptureEl: () => document.createElement('div'),
|
|
46
46
|
})
|
|
47
|
-
const handle = await item.resolve()
|
|
47
|
+
const handle = await item.resolve({ id: null })
|
|
48
48
|
expect(handle.url).toBe('blob:png')
|
|
49
49
|
expect(handle.filename).toBe('sales.png')
|
|
50
50
|
handle.revoke?.()
|
|
@@ -56,7 +56,9 @@ describe('buildPngDownloadItem', () => {
|
|
|
56
56
|
filename: 'demo',
|
|
57
57
|
getCaptureEl: () => null,
|
|
58
58
|
})
|
|
59
|
-
await expect(item.resolve()).rejects.toThrow(
|
|
59
|
+
await expect(item.resolve({ id: null })).rejects.toThrow(
|
|
60
|
+
/No PNG capture element/,
|
|
61
|
+
)
|
|
60
62
|
})
|
|
61
63
|
|
|
62
64
|
it('forwards pixelRatio and backgroundColor overrides', async () => {
|
|
@@ -66,7 +68,7 @@ describe('buildPngDownloadItem', () => {
|
|
|
66
68
|
pixelRatio: 4,
|
|
67
69
|
backgroundColor: '#fff',
|
|
68
70
|
})
|
|
69
|
-
const handle = await item.resolve()
|
|
71
|
+
const handle = await item.resolve({ id: null })
|
|
70
72
|
expect(handle.filename).toBe('sales.png')
|
|
71
73
|
})
|
|
72
74
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DOWNLOAD_ITEM_IDS } from './constants'
|
|
2
2
|
import { downloadDOMToPNG } from './exports'
|
|
3
3
|
import { PNGIcon } from './icons'
|
|
4
|
-
import type { DownloadItem } from './types'
|
|
4
|
+
import type { DownloadContext, DownloadItem } from './types'
|
|
5
5
|
|
|
6
6
|
export interface BuildPngDownloadItemArgs {
|
|
7
7
|
/** Base filename (without extension). The item appends `.png`. */
|
|
@@ -11,7 +11,7 @@ export interface BuildPngDownloadItemArgs {
|
|
|
11
11
|
* download config doesn't capture a stale reference. Wire it to
|
|
12
12
|
* `() => getCaptureEl(id)` from `widgets-v2/stores`.
|
|
13
13
|
*/
|
|
14
|
-
getCaptureEl: () => HTMLElement | null
|
|
14
|
+
getCaptureEl: (ctx: DownloadContext) => HTMLElement | null
|
|
15
15
|
/** html2canvas `scale`. Default 2. */
|
|
16
16
|
pixelRatio?: number
|
|
17
17
|
/** html2canvas `backgroundColor`. Default transparent (`null`). */
|
|
@@ -33,8 +33,8 @@ export function buildPngDownloadItem(
|
|
|
33
33
|
id: DOWNLOAD_ITEM_IDS.png,
|
|
34
34
|
label: args.label ?? 'PNG',
|
|
35
35
|
icon: <PNGIcon fontSize='small' />,
|
|
36
|
-
resolve: async () => {
|
|
37
|
-
const el = args.getCaptureEl()
|
|
36
|
+
resolve: async (ctx) => {
|
|
37
|
+
const el = args.getCaptureEl(ctx)
|
|
38
38
|
if (!el) {
|
|
39
39
|
throw new Error('[widgets-v2] No PNG capture element available')
|
|
40
40
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { ReactElement } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Passed to {@link DownloadItem.resolve} by `<Widget.Download>`.
|
|
5
|
+
*/
|
|
6
|
+
export interface DownloadContext {
|
|
7
|
+
/**
|
|
8
|
+
* Id of the widget store the menu was opened from — the dialog's own
|
|
9
|
+
* store when opened from `FullScreen.Slot`'s header, so an export matches
|
|
10
|
+
* what the user is looking at. `null` outside a `<Widget.Provider>`.
|
|
11
|
+
*/
|
|
12
|
+
id: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export interface DownloadItem {
|
|
4
16
|
/** Unique key (also used as menu label key). */
|
|
5
17
|
id: string
|
|
@@ -28,5 +40,7 @@ export interface DownloadItem {
|
|
|
28
40
|
* Resolves to a Blob URL the trigger will click. Optional `revoke` is
|
|
29
41
|
* called after the click is dispatched.
|
|
30
42
|
*/
|
|
31
|
-
resolve: (
|
|
43
|
+
resolve: (
|
|
44
|
+
ctx: DownloadContext,
|
|
45
|
+
) => Promise<{ url: string; filename: string; revoke?: () => void }>
|
|
32
46
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|
2
|
+
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
3
|
+
import { Provider } from '../../provider/widget-provider'
|
|
4
|
+
import type { ECharts } from 'echarts'
|
|
5
|
+
import {
|
|
6
|
+
clearAllWidgetStores,
|
|
7
|
+
getWidgetStore,
|
|
8
|
+
hasWidgetStore,
|
|
9
|
+
setEchartInstance,
|
|
10
|
+
useWidget,
|
|
11
|
+
useWidgetId,
|
|
12
|
+
} from '../../stores'
|
|
13
|
+
import { Download } from '../download/download'
|
|
14
|
+
import { FullScreen, fullScreenStoreId } from './fullscreen'
|
|
15
|
+
|
|
16
|
+
afterEach(() => clearAllWidgetStores())
|
|
17
|
+
|
|
18
|
+
// A stand-in for any action: reads and writes one flag on whichever store
|
|
19
|
+
// its copy runs on.
|
|
20
|
+
function Flag() {
|
|
21
|
+
const id = useWidgetId()
|
|
22
|
+
const on = useWidget(id, (s) => s.transformStates.flag?.enabled ?? false)
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
type='button'
|
|
26
|
+
data-testid={`flag-${id}`}
|
|
27
|
+
aria-pressed={on}
|
|
28
|
+
onClick={() =>
|
|
29
|
+
getWidgetStore(id).setState((s) => ({
|
|
30
|
+
transformStates: { ...s.transformStates, flag: { enabled: !on } },
|
|
31
|
+
}))
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
flag
|
|
35
|
+
</button>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function DataProbe() {
|
|
40
|
+
const id = useWidgetId()
|
|
41
|
+
const data = useWidget(id, (s) => s.rawData)
|
|
42
|
+
return <span data-testid={`data-${id}`}>{String(data)}</span>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Widget({ data = 'a' }: { data?: string }) {
|
|
46
|
+
return (
|
|
47
|
+
<Provider id='w' data={data}>
|
|
48
|
+
<FullScreen.Trigger labels={{ open: 'OpenT', close: 'CloseT' }} />
|
|
49
|
+
<FullScreen.Slot>
|
|
50
|
+
<Flag />
|
|
51
|
+
<DataProbe />
|
|
52
|
+
</FullScreen.Slot>
|
|
53
|
+
</Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const dialogId = fullScreenStoreId('w')
|
|
58
|
+
const flag = (id: string) =>
|
|
59
|
+
screen.getByTestId(`flag-${id}`).getAttribute('aria-pressed')
|
|
60
|
+
|
|
61
|
+
async function close() {
|
|
62
|
+
fireEvent.click(screen.getByLabelText('CloseT'))
|
|
63
|
+
await waitFor(() =>
|
|
64
|
+
expect(screen.queryByTestId(`flag-${dialogId}`)).toBeNull(),
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('<FullScreen.Slot> — the dialog runs on its own store', () => {
|
|
69
|
+
it('starts from the card, runs apart while open, and hands back on close', async () => {
|
|
70
|
+
render(<Widget />)
|
|
71
|
+
fireEvent.click(screen.getByTestId('flag-w'))
|
|
72
|
+
expect(flag('w')).toBe('true')
|
|
73
|
+
|
|
74
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
75
|
+
// Seeded from the card.
|
|
76
|
+
expect(flag(dialogId)).toBe('true')
|
|
77
|
+
|
|
78
|
+
// Changes in the dialog don't reach the card while open…
|
|
79
|
+
fireEvent.click(screen.getByTestId(`flag-${dialogId}`))
|
|
80
|
+
expect(flag(dialogId)).toBe('false')
|
|
81
|
+
expect(flag('w')).toBe('true')
|
|
82
|
+
|
|
83
|
+
// …and do once it closes. The dialog's store goes with it.
|
|
84
|
+
await close()
|
|
85
|
+
expect(flag('w')).toBe('false')
|
|
86
|
+
expect(hasWidgetStore(dialogId)).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('keeps the consumer props flowing into the dialog while it is open', () => {
|
|
90
|
+
const { rerender } = render(<Widget data='a' />)
|
|
91
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
92
|
+
expect(screen.getByTestId(`data-${dialogId}`).textContent).toBe('a')
|
|
93
|
+
rerender(<Widget data='b' />)
|
|
94
|
+
expect(screen.getByTestId(`data-${dialogId}`).textContent).toBe('b')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('reopening starts from the card again, not from the last dialog', async () => {
|
|
98
|
+
render(<Widget />)
|
|
99
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
100
|
+
await close()
|
|
101
|
+
act(() =>
|
|
102
|
+
getWidgetStore('w').setState({
|
|
103
|
+
transformStates: { flag: { enabled: true } },
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
106
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
107
|
+
expect(flag(dialogId)).toBe('true')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('downloads from the dialog header resolve against the dialog store', async () => {
|
|
111
|
+
const resolve = vi.fn(() =>
|
|
112
|
+
Promise.resolve({ url: 'blob:x', filename: 'x.csv' }),
|
|
113
|
+
)
|
|
114
|
+
render(
|
|
115
|
+
<Provider id='w' data='a'>
|
|
116
|
+
<FullScreen.Trigger labels={{ open: 'OpenT', close: 'CloseT' }} />
|
|
117
|
+
<FullScreen.Slot
|
|
118
|
+
actions={<Download items={[{ id: 'csv', label: 'CSV', resolve }]} />}
|
|
119
|
+
>
|
|
120
|
+
<span>body</span>
|
|
121
|
+
</FullScreen.Slot>
|
|
122
|
+
</Provider>,
|
|
123
|
+
)
|
|
124
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
125
|
+
fireEvent.click(screen.getByLabelText('Download'))
|
|
126
|
+
fireEvent.click(await screen.findByText('CSV'))
|
|
127
|
+
expect(resolve).toHaveBeenCalledWith({ id: dialogId })
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Legend / dataZoom / brush live in the chart instance, not the store.
|
|
131
|
+
// The Slot moves them through the instance registry, so Widget.Echart
|
|
132
|
+
// doesn't need to know about fullscreen.
|
|
133
|
+
it('starts the dialog chart from the card chart and hands it back on close', async () => {
|
|
134
|
+
const fakeChart = (option: object) =>
|
|
135
|
+
({
|
|
136
|
+
getOption: vi.fn(() => option),
|
|
137
|
+
setOption: vi.fn(),
|
|
138
|
+
dispatchAction: vi.fn(),
|
|
139
|
+
isDisposed: () => false,
|
|
140
|
+
}) as unknown as ECharts & {
|
|
141
|
+
setOption: ReturnType<typeof vi.fn>
|
|
142
|
+
}
|
|
143
|
+
const card = fakeChart({ from: 'card' })
|
|
144
|
+
const dialog = fakeChart({ from: 'dialog' })
|
|
145
|
+
render(<Widget />)
|
|
146
|
+
act(() => setEchartInstance('w', card))
|
|
147
|
+
|
|
148
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
149
|
+
act(() => setEchartInstance(dialogId, dialog))
|
|
150
|
+
expect(dialog.setOption).toHaveBeenCalledWith({ from: 'card' })
|
|
151
|
+
|
|
152
|
+
await close()
|
|
153
|
+
expect(card.setOption).toHaveBeenCalledWith({ from: 'dialog' })
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
2
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
3
|
-
import { fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
4
|
import { Provider } from '../../provider/widget-provider'
|
|
5
5
|
import { clearAllWidgetStores, getWidgetStore } from '../../stores'
|
|
6
6
|
import { FullScreen } from './fullscreen'
|
|
@@ -61,22 +61,35 @@ describe('<FullScreen.Trigger> — store toggle', () => {
|
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
describe('<FullScreen.Slot> — render-prop body + portal', () => {
|
|
64
|
-
it('passes the
|
|
65
|
-
let received: boolean | undefined
|
|
64
|
+
it('passes false to the card copy and true to the dialog copy', () => {
|
|
66
65
|
render(
|
|
67
66
|
<Provider id='fs-rp' data={[]}>
|
|
68
67
|
<FullScreen.Trigger />
|
|
69
68
|
<FullScreen.Slot>
|
|
70
|
-
{(isFullScreen) =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}}
|
|
69
|
+
{(isFullScreen) => (
|
|
70
|
+
<span data-testid={isFullScreen ? 'dialog' : 'card'}>body</span>
|
|
71
|
+
)}
|
|
74
72
|
</FullScreen.Slot>
|
|
75
73
|
</Provider>,
|
|
76
74
|
)
|
|
77
|
-
expect(
|
|
75
|
+
expect(screen.getByTestId('card')).toBeTruthy()
|
|
76
|
+
expect(screen.queryByTestId('dialog')).toBeNull()
|
|
78
77
|
fireEvent.click(screen.getByLabelText('Open in full screen'))
|
|
79
|
-
expect(
|
|
78
|
+
expect(screen.getByTestId('card')).toBeTruthy()
|
|
79
|
+
expect(screen.getByTestId('dialog')).toBeTruthy()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('renders plain children identically in both copies', () => {
|
|
83
|
+
render(
|
|
84
|
+
<Provider id='fs-plain' data={[]}>
|
|
85
|
+
<FullScreen.Trigger />
|
|
86
|
+
<FullScreen.Slot>
|
|
87
|
+
<span data-testid='body'>body</span>
|
|
88
|
+
</FullScreen.Slot>
|
|
89
|
+
</Provider>,
|
|
90
|
+
)
|
|
91
|
+
fireEvent.click(screen.getByLabelText('Open in full screen'))
|
|
92
|
+
expect(screen.getAllByTestId('body')).toHaveLength(2)
|
|
80
93
|
})
|
|
81
94
|
|
|
82
95
|
it('renders the title above the body when open', async () => {
|
|
@@ -104,47 +117,94 @@ describe('<FullScreen.Slot> — render-prop body + portal', () => {
|
|
|
104
117
|
expect(closes.length).toBeGreaterThan(0)
|
|
105
118
|
})
|
|
106
119
|
|
|
107
|
-
it('
|
|
120
|
+
it('renders `actions` in the modal header, next to the close button', async () => {
|
|
121
|
+
render(
|
|
122
|
+
<Provider id='fs-actions' data={[]}>
|
|
123
|
+
<FullScreen.Trigger labels={{ open: 'OpenT', close: 'CloseT' }} />
|
|
124
|
+
<FullScreen.Slot
|
|
125
|
+
title='With actions'
|
|
126
|
+
actions={<button type='button'>Download</button>}
|
|
127
|
+
>
|
|
128
|
+
{() => <div>body</div>}
|
|
129
|
+
</FullScreen.Slot>
|
|
130
|
+
</Provider>,
|
|
131
|
+
)
|
|
132
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
133
|
+
const action = await screen.findByText('Download')
|
|
134
|
+
// Same header row as the close button — the wrapper's action row is
|
|
135
|
+
// covered by the dialog, so the modal has to carry them itself.
|
|
136
|
+
const header = action.closest('.MuiDialogTitle-root')
|
|
137
|
+
expect(header).toBeTruthy()
|
|
138
|
+
expect(
|
|
139
|
+
header?.querySelector('[aria-label="Exit full screen"]'),
|
|
140
|
+
).toBeTruthy()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('keeps `actions` mounted across a fullscreen toggle', () => {
|
|
108
144
|
render(
|
|
109
|
-
<Provider id='fs-
|
|
145
|
+
<Provider id='fs-actions-keep' data={[]}>
|
|
146
|
+
<FullScreen.Trigger labels={{ open: 'OpenT', close: 'CloseT' }} />
|
|
147
|
+
<FullScreen.Slot
|
|
148
|
+
title='Y'
|
|
149
|
+
actions={<button type='button'>Download</button>}
|
|
150
|
+
>
|
|
151
|
+
{() => <div>body</div>}
|
|
152
|
+
</FullScreen.Slot>
|
|
153
|
+
</Provider>,
|
|
154
|
+
)
|
|
155
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
156
|
+
const before = screen.getByText('Download')
|
|
157
|
+
fireEvent.click(screen.getByLabelText('CloseT'))
|
|
158
|
+
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
159
|
+
expect(screen.getByText('Download')).toBe(before)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('merges `paperSx` over the default dialog paper size', () => {
|
|
163
|
+
render(
|
|
164
|
+
<Provider id='fs-paper' data={[]}>
|
|
110
165
|
<FullScreen.Trigger />
|
|
111
|
-
<FullScreen.Slot title='
|
|
112
|
-
{() => <
|
|
166
|
+
<FullScreen.Slot title='P' paperSx={{ width: 560, height: 'auto' }}>
|
|
167
|
+
{() => <div>body</div>}
|
|
113
168
|
</FullScreen.Slot>
|
|
114
169
|
</Provider>,
|
|
115
170
|
)
|
|
116
|
-
const before = screen.getByTestId('inner')
|
|
117
171
|
fireEvent.click(screen.getByLabelText('Open in full screen'))
|
|
118
|
-
const
|
|
119
|
-
expect(
|
|
172
|
+
const paper = document.querySelector<HTMLElement>('.MuiDialog-paper')
|
|
173
|
+
expect(paper).toBeTruthy()
|
|
174
|
+
const css = getComputedStyle(paper!)
|
|
175
|
+
expect(css.width).toBe('560px')
|
|
176
|
+
expect(css.height).toBe('auto')
|
|
177
|
+
// Untouched keys still come from the default — `90vw`, resolved against
|
|
178
|
+
// the test viewport (1024px).
|
|
179
|
+
expect(css.maxWidth).toBe('921.6px')
|
|
120
180
|
})
|
|
121
181
|
|
|
122
|
-
it('
|
|
123
|
-
let
|
|
182
|
+
it('keeps the card body mounted while the dialog mounts its own copy', async () => {
|
|
183
|
+
let mounts = 0
|
|
124
184
|
function CountingChild() {
|
|
125
|
-
const counted = useRef(false)
|
|
126
185
|
useEffect(() => {
|
|
127
|
-
|
|
128
|
-
counted.current = true
|
|
129
|
-
mountCount += 1
|
|
186
|
+
mounts += 1
|
|
130
187
|
}, [])
|
|
131
188
|
return <div data-testid='counter'>x</div>
|
|
132
189
|
}
|
|
133
|
-
// Distinct labels for the trigger so its "close" aria-label can't collide
|
|
134
|
-
// with the modal's own close button (also "Exit full screen" by default
|
|
135
|
-
// when keepMounted=true keeps the modal in DOM).
|
|
136
190
|
render(
|
|
137
|
-
<Provider id='fs-
|
|
191
|
+
<Provider id='fs-two-copies' data={[]}>
|
|
138
192
|
<FullScreen.Trigger labels={{ open: 'OpenT', close: 'CloseT' }} />
|
|
139
|
-
<FullScreen.Slot title='Z'>
|
|
193
|
+
<FullScreen.Slot title='Z'>
|
|
194
|
+
<CountingChild />
|
|
195
|
+
</FullScreen.Slot>
|
|
140
196
|
</Provider>,
|
|
141
197
|
)
|
|
142
|
-
|
|
198
|
+
const card = screen.getByTestId('counter')
|
|
199
|
+
expect(mounts).toBe(1)
|
|
143
200
|
fireEvent.click(screen.getByLabelText('OpenT'))
|
|
144
|
-
expect(
|
|
201
|
+
expect(mounts).toBe(2)
|
|
202
|
+
expect(screen.getAllByTestId('counter')).toHaveLength(2)
|
|
203
|
+
expect(screen.getAllByTestId('counter')).toContain(card)
|
|
145
204
|
fireEvent.click(screen.getByLabelText('CloseT'))
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
205
|
+
await waitFor(() =>
|
|
206
|
+
expect(screen.getAllByTestId('counter')).toHaveLength(1),
|
|
207
|
+
)
|
|
208
|
+
expect(screen.getByTestId('counter')).toBe(card)
|
|
149
209
|
})
|
|
150
210
|
})
|