-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Add alert/flash content replacement runtime API (#2647)
Co-authored-by: Andrei Zhaleznichenka <[email protected]>
- Loading branch information
1 parent
2a83d43
commit a039398
Showing
16 changed files
with
1,379 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import React, { useContext, useMemo, useState } from 'react'; | ||
import { render, unmountComponentAtNode } from 'react-dom'; | ||
|
||
import { | ||
Alert, | ||
AlertProps, | ||
Box, | ||
Button, | ||
Checkbox, | ||
ExpandableSection, | ||
FormField, | ||
Select, | ||
SpaceBetween, | ||
} from '~components'; | ||
import awsuiPlugins from '~components/internal/plugins'; | ||
|
||
import AppContext, { AppContextType } from '../app/app-context'; | ||
import ScreenshotArea from '../utils/screenshot-area'; | ||
|
||
type PageContext = React.Context<AppContextType<{ loading: boolean; hidden: boolean; type: AlertProps.Type }>>; | ||
|
||
awsuiPlugins.alertContent.registerContentReplacer({ | ||
id: 'awsui/alert-test-action', | ||
runReplacer(context, replacer) { | ||
console.log('mount'); | ||
|
||
const doReplace = () => { | ||
replacer.restoreHeader(); | ||
replacer.restoreContent(); | ||
if (context.type === 'error' && context.contentRef.current?.textContent?.match('Access denied')) { | ||
replacer.hideHeader(); | ||
replacer.replaceContent(container => { | ||
console.log('render replacement content'); | ||
render( | ||
<SpaceBetween size="s"> | ||
<Box>---REPLACEMENT--- Access denied message! ---REPLACEMENT---</Box> | ||
<ExpandableSection headerText="Original message"> | ||
{context.contentRef.current?.textContent} | ||
</ExpandableSection> | ||
</SpaceBetween>, | ||
container | ||
); | ||
}); | ||
} | ||
}; | ||
|
||
doReplace(); | ||
|
||
return { | ||
update() { | ||
console.log('update'); | ||
doReplace(); | ||
}, | ||
unmount({ replacementContentContainer }) { | ||
console.log('unmount'); | ||
unmountComponentAtNode(replacementContentContainer); | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
const alertTypeOptions = ['error', 'warning', 'info', 'success'].map(type => ({ value: type })); | ||
|
||
export default function () { | ||
const { | ||
urlParams: { loading = false, hidden = false, type = 'error' }, | ||
setUrlParams, | ||
} = useContext(AppContext as PageContext); | ||
const [unrelatedState, setUnrelatedState] = useState(false); | ||
const [contentSwapped, setContentSwapped] = useState(false); | ||
|
||
const content1 = useMemo(() => (loading ? <Box>Loading...</Box> : <Box>Content</Box>), [loading]); | ||
const content2 = loading ? <Box>Loading...</Box> : <Box>There was an error: Access denied because of XYZ</Box>; | ||
|
||
return ( | ||
<Box margin="m"> | ||
<h1>Alert runtime actions</h1> | ||
<SpaceBetween size="m"> | ||
<SpaceBetween size="s"> | ||
<Checkbox onChange={e => setUrlParams({ loading: e.detail.checked })} checked={loading}> | ||
Content loading | ||
</Checkbox> | ||
<Checkbox onChange={e => setUrlParams({ hidden: e.detail.checked })} checked={hidden}> | ||
Unmount all | ||
</Checkbox> | ||
<Checkbox onChange={e => setUnrelatedState(e.detail.checked)} checked={unrelatedState}> | ||
Unrelated state | ||
</Checkbox> | ||
<Checkbox onChange={e => setContentSwapped(e.detail.checked)} checked={contentSwapped}> | ||
Swap content | ||
</Checkbox> | ||
<FormField label="Alert type"> | ||
<Select | ||
options={alertTypeOptions} | ||
selectedOption={alertTypeOptions.find(option => option.value === type) ?? alertTypeOptions[0]} | ||
onChange={e => setUrlParams({ type: e.detail.selectedOption.value as AlertProps.Type })} | ||
/> | ||
</FormField> | ||
</SpaceBetween> | ||
|
||
<hr /> | ||
|
||
<ScreenshotArea gutters={false}> | ||
{hidden ? null : ( | ||
<SpaceBetween size="m"> | ||
<Alert | ||
type={type} | ||
statusIconAriaLabel={type} | ||
dismissAriaLabel="Dismiss" | ||
header="Header" | ||
action={<Button>Action</Button>} | ||
> | ||
{!contentSwapped ? content1 : content2} | ||
</Alert> | ||
|
||
<Alert | ||
type={type} | ||
statusIconAriaLabel={type} | ||
dismissAriaLabel="Dismiss" | ||
header="Header" | ||
action={<Button>Action</Button>} | ||
> | ||
{!contentSwapped ? content2 : content1} | ||
</Alert> | ||
</SpaceBetween> | ||
)} | ||
</ScreenshotArea> | ||
</SpaceBetween> | ||
</Box> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import React, { useContext, useState } from 'react'; | ||
import { render, unmountComponentAtNode } from 'react-dom'; | ||
|
||
import { | ||
Box, | ||
Button, | ||
Checkbox, | ||
ExpandableSection, | ||
Flashbar, | ||
FlashbarProps, | ||
FormField, | ||
Select, | ||
SpaceBetween, | ||
} from '~components'; | ||
import awsuiPlugins from '~components/internal/plugins'; | ||
|
||
import AppContext, { AppContextType } from '../app/app-context'; | ||
import ScreenshotArea from '../utils/screenshot-area'; | ||
|
||
type PageContext = React.Context< | ||
AppContextType<{ loading: boolean; hidden: boolean; stackItems: boolean; type: FlashbarProps.Type }> | ||
>; | ||
|
||
awsuiPlugins.flashContent.registerContentReplacer({ | ||
id: 'awsui/flashbar-test-action', | ||
runReplacer(context, replacer) { | ||
console.log('mount'); | ||
|
||
const doReplace = () => { | ||
replacer.restoreHeader(); | ||
replacer.restoreContent(); | ||
if (context.type === 'error' && context.contentRef.current?.textContent?.match('Access denied')) { | ||
replacer.hideHeader(); | ||
replacer.replaceContent(container => { | ||
console.log('render replacement content'); | ||
render( | ||
<SpaceBetween size="s"> | ||
<Box>---REPLACEMENT--- Access denied message! ---REPLACEMENT---</Box> | ||
<ExpandableSection headerText="Original message"> | ||
{context.contentRef.current?.textContent} | ||
</ExpandableSection> | ||
</SpaceBetween>, | ||
container | ||
); | ||
}); | ||
} | ||
}; | ||
|
||
doReplace(); | ||
|
||
return { | ||
update() { | ||
console.log('update'); | ||
doReplace(); | ||
}, | ||
unmount({ replacementContentContainer }) { | ||
console.log('unmount'); | ||
unmountComponentAtNode(replacementContentContainer); | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
const messageTypeOptions = ['error', 'warning', 'info', 'success'].map(type => ({ value: type })); | ||
|
||
export default function () { | ||
const { | ||
urlParams: { loading = false, hidden = false, stackItems = false, type = 'error' }, | ||
setUrlParams, | ||
} = useContext(AppContext as PageContext); | ||
const [unrelatedState, setUnrelatedState] = useState(false); | ||
|
||
return ( | ||
<Box margin="m"> | ||
<h1>Flashbar runtime actions</h1> | ||
<SpaceBetween size="m"> | ||
<SpaceBetween size="s"> | ||
<Checkbox onChange={e => setUrlParams({ loading: e.detail.checked })} checked={loading}> | ||
Content loading | ||
</Checkbox> | ||
<Checkbox onChange={e => setUrlParams({ hidden: e.detail.checked })} checked={hidden}> | ||
Unmount all | ||
</Checkbox> | ||
<Checkbox onChange={e => setUrlParams({ stackItems: e.detail.checked })} checked={stackItems}> | ||
Stack items | ||
</Checkbox> | ||
<Checkbox onChange={e => setUnrelatedState(e.detail.checked)} checked={unrelatedState}> | ||
Unrelated state | ||
</Checkbox> | ||
<FormField label="Message type"> | ||
<Select | ||
options={messageTypeOptions} | ||
selectedOption={messageTypeOptions.find(option => option.value === type) ?? messageTypeOptions[0]} | ||
onChange={e => setUrlParams({ type: e.detail.selectedOption.value as FlashbarProps.Type })} | ||
/> | ||
</FormField> | ||
</SpaceBetween> | ||
|
||
<hr /> | ||
|
||
<ScreenshotArea gutters={false}> | ||
{hidden ? null : ( | ||
<Flashbar | ||
stackItems={stackItems} | ||
items={[ | ||
{ | ||
type, | ||
statusIconAriaLabel: type, | ||
header: 'Header', | ||
content: loading ? 'Loading...' : 'Content', | ||
action: <Button>Action</Button>, | ||
}, | ||
{ | ||
type, | ||
statusIconAriaLabel: type, | ||
header: 'Header', | ||
content: loading ? 'Loading...' : 'There was an error: Access denied because of XYZ', | ||
action: <Button>Action</Button>, | ||
}, | ||
]} | ||
/> | ||
)} | ||
</ScreenshotArea> | ||
</SpaceBetween> | ||
</Box> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import { AlertWrapper } from '../../../lib/components/test-utils/dom'; | ||
import FlashWrapper from '../../../lib/components/test-utils/dom/flashbar/flash.js'; | ||
|
||
import alertStyles from '../../../lib/components/alert/styles.selectors.js'; | ||
import flashbarStyles from '../../../lib/components/flashbar/styles.selectors.js'; | ||
|
||
export const expectContent = ( | ||
wrapper: AlertWrapper | FlashWrapper, | ||
stylesCss: Record<string, string>, | ||
{ | ||
header, | ||
headerReplaced, | ||
content, | ||
contentReplaced, | ||
}: { | ||
header?: string | false; | ||
headerReplaced?: boolean; | ||
content?: string | false; | ||
contentReplaced?: boolean; | ||
} | ||
) => { | ||
if (header) { | ||
if (headerReplaced) { | ||
if (wrapper.findHeader()) { | ||
expect(wrapper.findHeader()?.getElement()).toHaveClass(stylesCss.hidden); | ||
} | ||
expect(findReplacementHeader(wrapper)?.getElement().textContent).toBe(header); | ||
} else { | ||
expect(findReplacementHeader(wrapper)?.getElement()).toHaveClass(stylesCss.hidden); | ||
expect(wrapper.findHeader()?.getElement().textContent).toBe(header); | ||
} | ||
} else if (header === false) { | ||
if (wrapper.findHeader()) { | ||
expect(wrapper.findHeader()?.getElement()).toHaveClass(stylesCss.hidden); | ||
} | ||
expect(findReplacementHeader(wrapper)?.getElement()).toHaveClass(stylesCss.hidden); | ||
} | ||
if (content) { | ||
if (contentReplaced) { | ||
expect(wrapper.findContent()?.getElement()).toHaveClass(stylesCss.hidden); | ||
expect(findReplacementContent(wrapper)?.getElement().textContent).toBe(content); | ||
} else { | ||
expect(findReplacementContent(wrapper)?.getElement()).toHaveClass(stylesCss.hidden); | ||
expect(wrapper.findContent()?.getElement().textContent).toBe(content); | ||
} | ||
} else if (content === false) { | ||
expect(wrapper.findContent()?.getElement()).toHaveClass(stylesCss.hidden); | ||
expect(findReplacementContent(wrapper)?.getElement()).toHaveClass(stylesCss.hidden); | ||
} | ||
}; | ||
|
||
function findReplacementHeader(wrapper: AlertWrapper | FlashWrapper) { | ||
const styles = wrapper instanceof AlertWrapper ? alertStyles : flashbarStyles; | ||
return wrapper.findByClassName(styles['header-replacement']); | ||
} | ||
|
||
function findReplacementContent(wrapper: AlertWrapper | FlashWrapper) { | ||
const styles = wrapper instanceof AlertWrapper ? alertStyles : flashbarStyles; | ||
return wrapper.findByClassName(styles['content-replacement'])!; | ||
} |
Oops, something went wrong.