Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
bebd54f
feat(dialog): initial implementation
igdmdimitrov Apr 27, 2022
da283ba
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Apr 27, 2022
fdd723e
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff May 4, 2022
0b1e4c7
refactor(dialog): fix markup and add light themes
simeonoff May 4, 2022
536fce9
refactor(dialog): add dark themes
simeonoff May 5, 2022
ce50362
fix(dialog): bootstrap shows when closed
simeonoff May 5, 2022
365f6da
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff May 9, 2022
c19fb82
Merge branch 'master' into dmdimitrov/dialog-component
rkaraivanov May 20, 2022
18ed1b9
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Jun 20, 2022
8e5011c
feat(dialog): component implementation
onlyexeption Jun 28, 2022
3f25164
feat(dialog): handle open state
onlyexeption Jun 29, 2022
a9f8f23
feat(dialog): add role and aria props
onlyexeption Jun 30, 2022
7ce0bae
chore: add default value to aria-labelledby
onlyexeption Jun 30, 2022
66c2b32
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Jun 30, 2022
7bf8922
feat(dialog): added tests
igdmdimitrov Jul 1, 2022
eb0071f
push latest changes
onlyexeption Jul 5, 2022
85463df
Merge branch 'master' into dmdimitrov/dialog-component
onlyexeption Jul 5, 2022
6ff0ca9
push latest changes
onlyexeption Jul 6, 2022
1746411
fix role property
onlyexeption Jul 6, 2022
76a3ce5
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff Jul 15, 2022
b039383
Merge branch 'master' into dmdimitrov/dialog-component
rkaraivanov Jul 25, 2022
81a6d54
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Jul 29, 2022
7c8d141
feat(dialog): update dialog themes
didimmova Aug 3, 2022
b55494c
feat(dialog): remove dark themes
didimmova Aug 3, 2022
d2f49c2
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff Aug 8, 2022
8b2b4ed
feat(dialog): fix backdrop
didimmova Aug 8, 2022
e96cbb1
test(dialog): fix failing test
didimmova Aug 8, 2022
c004a24
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff Aug 9, 2022
d3954c7
feat(dialog): use hidden attribute
didimmova Aug 9, 2022
6cd62c5
Merge branch 'dmdimitrov/dialog-component' of https://github.com/Igni…
didimmova Aug 9, 2022
cea8aa2
Merge branch 'master' into dmdimitrov/dialog-component
simeonoff Aug 9, 2022
a48673f
test(dialog): fix test
didimmova Aug 9, 2022
5e5db61
Merge branch 'master' into dmdimitrov/dialog-component
Aug 11, 2022
5ffc60c
Merge branch 'master' into dmdimitrov/dialog-component
rkaraivanov Aug 18, 2022
a9a4b53
chore(*): refactor and address PR comments
igdmdimitrov Aug 19, 2022
b28027c
chore(*): address PR comments
igdmdimitrov Aug 24, 2022
cf41764
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Aug 24, 2022
2242282
feat(dialog): render ok button if no content
igdmdimitrov Aug 25, 2022
567f9ce
refactor: Dialog features
rkaraivanov Aug 26, 2022
f03a8ee
refactor: Default action button location
rkaraivanov Aug 29, 2022
3997cfd
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Aug 29, 2022
f4d0d7d
feat(dialog): add more tests
igdmdimitrov Aug 29, 2022
2e01fd7
Merge branch 'master' into dmdimitrov/dialog-component
igdmdimitrov Aug 30, 2022
9b0bff1
refactor: Default action button and events
rkaraivanov Aug 31, 2022
344ea88
Merge branch 'master' into dmdimitrov/dialog-component
ChronosSF Sep 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 346 additions & 0 deletions src/components/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
import {
elementUpdated,
expect,
fixture,
html,
unsafeStatic,
} from '@open-wc/testing';
import sinon from 'sinon';
import { defineComponents, IgcDialogComponent } from '../../index.js';

describe('Dialog component', () => {
const fireMouseEvent = (type: string, opts: MouseEventInit) =>
new MouseEvent(type, opts);
const getBoundingRect = (el: Element) => el.getBoundingClientRect();

before(() => {
defineComponents(IgcDialogComponent);
});

let dialog: IgcDialogComponent;
let dialogEl: HTMLDialogElement;

describe('', () => {
beforeEach(async () => {
dialog = await createDialogComponent();
dialogEl = dialog.shadowRoot!.querySelector(
'dialog'
) as HTMLDialogElement;
});

it('passes the a11y audit', async () => {
const dialog = await fixture<IgcDialogComponent>(
html`<igc-dialog></igc-dialog>`
);

await expect(dialog).shadowDom.to.be.accessible();
});

it('should render content inside the dialog', async () => {
const content = 'Dialog content';
dialog = await createDialogComponent(
`<igc-dialog><span>${content}</span></igc-dialog>`
);

dialog.show();
await elementUpdated(dialog);

expect(dialog).dom.to.have.text(content);
expect(dialog).dom.to.equal(
`
<igc-dialog>
<span>
Dialog content
</span>
</igc-dialog>`,
Comment on lines +48 to +55
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused. No way this is actually testing if the dialog has the content since it's not looking in the shadow DOM - so not really checking if the content is inside the dialog for real.
Same line of thinking for the second check - which again tests the light DOM for the very same nodes that were used in createDialogComponent, thus testing if the fixture and native DOM node creation works, kinda pointless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: Tested by deleting the default slot in the dialog, this test still passed :)

{
ignoreAttributes: [
'variant',
'aria-label',
'aria-disabled',
'aria-hidden',
'aria-labelledby',
'part',
'role',
'size',
'id',
'hidden',
'open',
],
}
);
});

it('`hide-default-action` correctly toggles DOM structure', async () => {
dialog = await fixture<IgcDialogComponent>(
html`<igc-dialog>Message</igc-dialog>`
);

const footer = dialog.shadowRoot!.querySelector('footer');

expect(footer).dom.to.equal(
`<footer>
<slot name="footer">
<igc-button>OK</igc-button>
</slot>
</footer>`,
{
ignoreAttributes: ['part', 'variant', 'size'],
}
);

dialog.hideDefaultAction = true;
await elementUpdated(dialog);

expect(footer).dom.to.equal(
`<footer>
<slot name="footer">
</slot>
</footer>`,
{
ignoreAttributes: ['part'],
}
);
});

it('renders a dialog element internally with default button if no content is provided', async () => {
await expect(dialog).shadowDom.to.be.accessible();
expect(dialog).shadowDom.to.equal(
`
<div></div>
<dialog>
<header>
<slot name="title"><span></span></slot>
</header>
<section>
<slot></slot>
</section>
<footer>
<slot name="footer">
<igc-button>
OK
</igc-button>
</slot>
</footer>
</dialog>`,
{
ignoreAttributes: [
'variant',
'aria-label',
'aria-disabled',
'aria-hidden',
'aria-labelledby',
'part',
'role',
'size',
'id',
'hidden',
],
}
);
});

it('show method should open the dialog', async () => {
dialog.open = false;
await elementUpdated(dialog);

dialog.show();
await elementUpdated(dialog);
expect(dialog.open).to.eq(true);
});

it('hide method should close the dialog', async () => {
dialog.open = true;
await elementUpdated(dialog);

dialog.hide();
await elementUpdated(dialog);
expect(dialog.open).to.eq(false);
});

it('toggle method should toggle the dialog', async () => {
dialog.open = true;
await elementUpdated(dialog);

dialog.toggle();
await elementUpdated(dialog);
expect(dialog.open).to.eq(false);

dialog.open = false;
await elementUpdated(dialog);

dialog.toggle();
await elementUpdated(dialog);
expect(dialog.open).to.eq(true);
});

it('is created with the proper default values', async () => {
expect(dialog.closeOnEscape).to.equal(true);
expect(dialog.closeOnOutsideClick).to.equal(false);
expect(dialog.title).to.be.undefined;
expect(dialog.open).to.equal(false);
expect(dialog.returnValue).to.be.undefined;

const header = dialog.shadowRoot?.querySelector('header') as HTMLElement;
expect(dialogEl.getAttribute('aria-labelledby')).to.equal(
header.getAttribute('id')
);
});

it('has correct aria label and role', async () => {
dialog.ariaLabel = 'ariaLabel';
dialog.open = true;
await elementUpdated(dialog);

expect(dialogEl.getAttribute('aria-label')).to.equal('ariaLabel');
expect(dialogEl.getAttribute('role')).to.equal('dialog');
});

it('does not emit events through API calls', async () => {
const spy = sinon.spy(dialog, 'emitEvent');
dialog.show();
await elementUpdated(dialog);

expect(dialog.open).to.be.true;
expect(spy.callCount).to.equal(0);

dialog.hide();
await elementUpdated(dialog);

expect(dialog.open).to.be.false;
expect(spy.callCount).to.equal(0);

dialog.open = true;
await elementUpdated(dialog);

expect(dialog.open).to.be.true;
expect(spy.callCount).to.equal(0);

dialog.open = false;
await elementUpdated(dialog);

expect(dialog.open).to.be.false;
expect(spy.callCount).to.equal(0);
});

it('default action button emits closing events', async () => {
const spy = sinon.spy(dialog, 'emitEvent');
dialog.show();
await elementUpdated(dialog);

dialog.shadowRoot!.querySelector('igc-button')!.click();
await elementUpdated(dialog);

expect(spy.callCount).to.equal(2);
expect(spy.firstCall).calledWith('igcClosing');
expect(spy.secondCall).calledWith('igcClosed');
});

it('cancels closing event correctly', async () => {
dialog.toggle();
await elementUpdated(dialog);
expect(dialog.open).to.be.true;

const eventSpy = sinon.spy(dialog, 'emitEvent');

dialog.addEventListener('igcClosing', (ev) => {
ev.preventDefault();
});

dialog.shadowRoot!.querySelector('igc-button')!.click();
await elementUpdated(dialog);
expect(eventSpy).calledOnceWith('igcClosing');
});

it('can cancel `igcClosing` event when clicking outside', async () => {
dialog.show();
await elementUpdated(dialog);

dialog.closeOnOutsideClick = true;
await elementUpdated(dialog);

const eventSpy = sinon.spy(dialog, 'emitEvent');
dialog.addEventListener('igcClosing', (e) => e.preventDefault());

const { x, y } = getBoundingRect(dialog);
dialogEl.dispatchEvent(
fireMouseEvent('click', {
bubbles: true,
composed: true,
clientX: x - 1,
clientY: y - 1,
})
);
await elementUpdated(dialog);

expect(eventSpy).calledWith('igcClosing');
expect(eventSpy).not.calledWith('igcClosed');
});

it('does not close the dialog on clicking outside when `closeOnOutsideClick` is false.', async () => {
dialog.show();
await elementUpdated(dialog);

dialog.closeOnOutsideClick = false;
await elementUpdated(dialog);

const { x, y } = getBoundingRect(dialog);
dialogEl.dispatchEvent(
fireMouseEvent('click', {
bubbles: true,
composed: true,
clientX: x - 1,
clientY: y - 1,
})
);
await elementUpdated(dialog);

expect(dialog.open).to.be.true;
});

it('closes the dialog on clicking outside when `closeOnOutsideClick` is true.', async () => {
dialog.show();
await elementUpdated(dialog);

dialog.closeOnOutsideClick = true;
await elementUpdated(dialog);

const { x, y } = getBoundingRect(dialog);
dialogEl.dispatchEvent(
fireMouseEvent('click', {
bubbles: true,
composed: true,
clientX: x - 1,
clientY: y - 1,
})
);
await elementUpdated(dialog);

expect(dialog.open).to.be.false;
});

it('closes the dialog when form with method=dialog is submitted', async () => {
dialog = await createDialogComponent(`
<igc-dialog>
<igc-form id="form" method="dialog">
<igc-button type="submit">Confirm</igc-button>
</igc-form>
</igc-dialog>
`);
await elementUpdated(dialog);

dialog.show();
await elementUpdated(dialog);

const form = document.getElementById('form');
form?.dispatchEvent(new Event('igcSubmit'));
await elementUpdated(dialog);

expect(dialog.open).to.eq(false);
});

const createDialogComponent = (template = `<igc-dialog></igc-dialog>`) => {
return fixture<IgcDialogComponent>(html`${unsafeStatic(template)}`);
};
});
});
Loading