Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions components/page/page-divider-internal.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import '../colors/colors.js';
import { css, html, LitElement } from 'lit';
import { FocusMixin } from '../../mixins/focus/focus-mixin.js';
import { formatPercent } from '@brightspace-ui/intl';
import { ifDefined } from 'lit/directives/if-defined.js';
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';

export const DIVIDER_WIDTH = 4;
const KEYBOARD_STEP = 20; // TO DO: Confirm
const KEYBOARD_STEP_LARGE = 80; // TO DO: Confirm
export const KEYBOARD_STEP = 20; // TO DO: Confirm
export const KEYBOARD_STEP_LARGE = 80; // TO DO: Confirm

const clampedSize = (size, min, max) => Math.max(min, Math.min(size, max));

/**
* Internal divider used by d2l-page to resize its side-nav and supporting panels.
*/
class PageDivider extends PropertyRequiredMixin(LitElement) {
class PageDivider extends FocusMixin(PropertyRequiredMixin(LitElement)) {

static properties = {
/**
Expand Down Expand Up @@ -77,6 +78,8 @@ class PageDivider extends PropertyRequiredMixin(LitElement) {

`;

static focusElementSelector = '.divider';

constructor() {
super();

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions components/page/test/page-divider-internal-fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createPage } from './page-fixtures.js';
import { html } from 'lit';

export function createDivider({
currentSize = 450,
maxSize = 600,
minSize = 320,
panelType = 'panel',
panelPosition = 'start'
} = {}) {
return html`
<d2l-page-divider-internal
label="Resize"
current-size="${currentSize}"
min-size="${minSize}"
max-size="${maxSize}"
panel-type="${panelType}"
panel-position="${panelPosition}">
</d2l-page-divider-internal>
`;
}

function createDividerPage(options) {
return createPage({
mainHeight: '100px',
sideNavHeight: '100px',
supportingHeight: '100px',
...options
});
}

export function getDivider(elem, panelKey) {
return elem.shadowRoot.querySelector(`d2l-page-divider-internal[data-panel-key="${panelKey}"]`);
}

export function getSlider(elem) {
return elem.shadowRoot.querySelector('.divider');
}

export const pageDividerFixtures = {
sideNavBothHeaders: createDividerPage({ layout: 'side-nav', hasMainHeader: true, hasSideNavHeader: true }),
sideNavBothHeadersFooter: createDividerPage({ layout: 'side-nav', hasMainHeader: true, hasSideNavHeader: true, hasFooter: true }),
sideNavLongMainBothHeaders: createDividerPage({ layout: 'side-nav', hasMainHeader: true, hasSideNavHeader: true, mainHeight: '400px' }),
sideNavImmersiveFooter: createDividerPage({ layout: 'side-nav', header: 'immersive', hasFooter: true }),
sideNavImmersiveLongFooter: createDividerPage({ layout: 'side-nav', header: 'immersive', hasFooter: true, sideNavHeight: '400px' }),
supportingLongFooter: createDividerPage({ layout: 'supporting', hasFooter: true, supportingHeight: '400px' }),
supportingImmersiveLongMain: createDividerPage({ layout: 'supporting', header: 'immersive', mainHeight: '400px' }),
supportingImmersiveFooter: createDividerPage({ layout: 'supporting', header: 'immersive', hasFooter: true }),
supportingImmersiveBothHeaders: createDividerPage({ layout: 'supporting', header: 'immersive', hasMainHeader: true, hasSupportingHeader: true }),
};
19 changes: 19 additions & 0 deletions components/page/test/page-divider-internal.axe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import '../page-divider-internal.js';
import { clickElem, expect, fixture, hoverElem } from '@brightspace-ui/testing';
import { getDivider, pageDividerFixtures } from './page-divider-internal-fixtures.js';

describe('page-divider-internal', () => {

it('hover', async() => {
const elem = await fixture(pageDividerFixtures.supportingImmersiveFooter);
await hoverElem(getDivider(elem, 'supporting'));
await expect(elem).to.be.accessible();
});

it('focus', async() => {
const elem = await fixture(pageDividerFixtures.sideNavBothHeadersFooter);
await clickElem(getDivider(elem, 'side-nav'));
await expect(elem).to.be.accessible();
});

});
131 changes: 131 additions & 0 deletions components/page/test/page-divider-internal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { expect, fixture, html, nextFrame, oneEvent, runConstructor, sendKeysElem } from '@brightspace-ui/testing';
import { KEYBOARD_STEP, KEYBOARD_STEP_LARGE } from '../page-divider-internal.js';
import { createDivider } from './page-divider-internal-fixtures.js';

describe('d2l-page-divider-internal', () => {

it('should construct', () => {
runConstructor('d2l-page-divider-internal');
});

describe('accessibility', () => {
it('calculates aria values correctly', async() => {
const elem = await fixture(html`<d2l-page-divider-internal label="Resize" min-size="100" max-size="200" current-size="150"></d2l-page-divider-internal>`);
const slider = elem.shadowRoot.querySelector('.divider');
expect(slider.getAttribute('aria-valuemax')).to.equal('200');
expect(slider.getAttribute('aria-valuemin')).to.equal('0');
expect(slider.getAttribute('aria-valuenow')).to.equal('150');
expect(slider.getAttribute('aria-valuetext')).to.equal('75 %');
});

it('does not set aria values when in unknown state', async() => {
const elem = await fixture(html`<d2l-page-divider-internal label="Resize"></d2l-page-divider-internal>`);
const slider = elem.shadowRoot.querySelector('.divider');
expect(slider.hasAttribute('aria-valuemax')).to.be.false;
expect(slider.hasAttribute('aria-valuemin')).to.be.false;
expect(slider.hasAttribute('aria-valuenow')).to.be.false;
expect(slider.hasAttribute('aria-valuetext')).to.be.false;
});
});

describe('events', () => {
describe('d2l-page-divider-toggle', () => {
describe('keyboard', () => {
['Enter', ' '].forEach(key => {
it(`dispatches d2l-page-divider-toggle on "${key === ' ' ? 'Space' : key}"`, async() => {
const elem = await fixture(createDivider());
sendKeysElem(elem, 'press', key);
const e = await oneEvent(elem, 'd2l-page-divider-toggle');
expect(e.detail).to.be.null; // TO DO: Will eventually have the collapse state
});
});
});

describe('mouse', () => {
// TO DO
});

});

describe('d2l-page-divider-resize', () => {
describe('keyboard', () => {
const currentSize = 450;
const minSize = 320;
const maxSize = 600;
const step = KEYBOARD_STEP;
const halfStep = Math.ceil(step / 2);
const largeStep = KEYBOARD_STEP_LARGE;
const halfLargeStep = Math.ceil(largeStep / 2);

[
{ name: 'start panel', panelType: 'panel', panelPosition: 'start', grow: 'ArrowRight', shrink: 'ArrowLeft', inactiveKeys: ['ArrowUp', 'ArrowDown'] },
{ name: 'end panel', panelType: 'panel', panelPosition: 'end', grow: 'ArrowLeft', shrink: 'ArrowRight', inactiveKeys: ['ArrowUp', 'ArrowDown'] },
{ name: 'drawer', panelType: 'drawer', grow: 'ArrowUp', shrink: 'ArrowDown', inactiveKeys: ['ArrowLeft', 'ArrowRight'] },
{ name: 'start panel in rtl', rtl: true, panelType: 'panel', panelPosition: 'start', grow: 'ArrowLeft', shrink: 'ArrowRight', inactiveKeys: ['ArrowUp', 'ArrowDown'] },
{ name: 'end panel in rtl', rtl: true, panelType: 'panel', panelPosition: 'end', grow: 'ArrowRight', shrink: 'ArrowLeft', inactiveKeys: ['ArrowUp', 'ArrowDown'] },
{ name: 'drawer rtl', rtl: true, panelType: 'drawer', grow: 'ArrowUp', shrink: 'ArrowDown', inactiveKeys: ['ArrowLeft', 'ArrowRight'] },
].forEach(test => {

describe(test.name, () => {

[
{ action: 'grow', key: test.grow, expectedSize: currentSize + step },
{ action: 'shrink', key: test.shrink, expectedSize: currentSize - step },
{ action: 'large grow', key: 'PageUp', expectedSize: currentSize + largeStep },
{ action: 'large shrink', key: 'PageDown', expectedSize: currentSize - largeStep },
{ action: 'max', key: 'End', expectedSize: maxSize },
{ action: 'min', key: 'Home', expectedSize: minSize }
].forEach(({ action, key, expectedSize }) => {
it(`dispatches event with requestedSize ${expectedSize} when "${action}" action requested (Key: ${key})`, async() => {
const elem = await fixture(
createDivider({ panelType: test.panelType, panelPosition: test.panelPosition }),
{ rtl: test.rtl }
);
sendKeysElem(elem, 'press', key);
const e = await oneEvent(elem, 'd2l-page-divider-resize');
expect(e.detail.requestedSize).to.equal(expectedSize);
});
});

[
{ action: 'grow', key: test.grow, currentSize: maxSize - halfStep, expectedSize: maxSize },
{ action: 'shrink', key: test.shrink, currentSize: minSize + halfStep, expectedSize: minSize },
{ action: 'large grow', key: 'PageUp', currentSize: maxSize - halfLargeStep, expectedSize: maxSize },
{ action: 'large shrink', key: 'PageDown', currentSize: minSize + halfLargeStep, expectedSize: minSize },
{ action: 'max', key: 'End', currentSize: maxSize, expectedSize: maxSize },
{ action: 'min', key: 'Home', currentSize: minSize, expectedSize: minSize }
].forEach(({ action, key, currentSize, expectedSize }) => {
it(`does not ${action} past limits`, async() => {
const elem = await fixture(
createDivider({ currentSize, panelType: test.panelType, panelPosition: test.panelPosition }),
{ rtl: test.rtl }
);
sendKeysElem(elem, 'press', key);
const e = await oneEvent(elem, 'd2l-page-divider-resize');
expect(e.detail.requestedSize).to.equal(expectedSize);
});
});

it('does not dispatch event when inactive keys pressed', async() => {
const elem = await fixture(
createDivider({ panelType: test.panelType, panelPosition: test.panelPosition }),
{ rtl: test.rtl }
);
let dispatched = false;
elem.addEventListener('d2l-page-divider-resize', () => dispatched = true);
for (const key of test.inactiveKeys) {
await sendKeysElem(elem, 'press', key);
}
await nextFrame();
expect(dispatched).to.be.false;
});
});
});
});

describe('mouse', () => {
// TO DO
});
});
});
});
62 changes: 62 additions & 0 deletions components/page/test/page-divider-internal.vdiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expect, fixture, focusElem, hoverElem } from '@brightspace-ui/testing';
import { getDivider, pageDividerFixtures } from './page-divider-internal-fixtures.js';

describe('page-divider-internal', () => {

describe('hover', () => {
[
{ name: 'side-nav', divider: 'side-nav', fixture: pageDividerFixtures.sideNavBothHeaders },
{ name: 'supporting-immersive', divider: 'supporting', fixture: pageDividerFixtures.supportingImmersiveFooter }
].forEach(test => {
it(test.name, async() => {
const elem = await fixture(test.fixture, { pagePadding: false, viewport: { width: 1000, height: 400 } });
await hoverElem(getDivider(elem, test.divider));
await expect(elem).to.be.golden({ margin: 0 });

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.

I guess it's necessary for all of these to screenshot the whole page vs. just the divider? Just wondering if we could save some time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah I thought about it, but most of the visual part of the divider worth testing is how it interacts with the page. hover could probably move out, but that's assuming we don't end up adding any additional elements to help with dragging/selecting on hover.

I tried to make sure there's not duplication - page vdiffs has no focus/hover tests for divider, and divider vdiffs don't test the default state (which is covered by page vdiffs).

I could probably make them shorter though! And a tad less wide, but not much.

});
});
});

describe('focus', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The variety of focus and focus-scrolled vdiffs is kinda pointless right now, but will eventually be important to show where the collapse button and floating arrows live in the different scenarios.

[
{ name: 'side-nav', divider: 'side-nav', fixture: pageDividerFixtures.sideNavBothHeadersFooter },
{ name: 'side-nav-immersive', divider: 'side-nav', fixture: pageDividerFixtures.sideNavImmersiveFooter },
{ name: 'side-nav-long-no-scroll', divider: 'side-nav', fixture: pageDividerFixtures.sideNavLongMainBothHeaders },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

page-divider-internal focus side-nav-long-no-scroll, page-divider-internal focus supporting, and page-divider-internal focus supporting-immersive-long-no-scroll are currently scrolled when they shouldn't be. This is because of https://desire2learn.atlassian.net/browse/GAUD-10291, and will be fixed in the next PR.

{ name: 'supporting', divider: 'supporting', fixture: pageDividerFixtures.supportingLongFooter },
{ name: 'supporting-immersive', divider: 'supporting', fixture: pageDividerFixtures.supportingImmersiveBothHeaders },
{ name: 'supporting-immersive-long-no-scroll', divider: 'supporting', fixture: pageDividerFixtures.supportingImmersiveLongMain }
].forEach(test => {
it(test.name, async() => {
const elem = await fixture(test.fixture, { pagePadding: false, viewport: { width: 1000, height: 400 } });
await focusElem(getDivider(elem, test.divider));
await expect(elem).to.be.golden({ margin: 0 });
});
});
});

describe('focus-scrolled', () => {
[
{ name: 'main', divider: 'side-nav', fixture: pageDividerFixtures.sideNavLongMainBothHeaders },
{ name: 'panel', divider: 'supporting', fixture: pageDividerFixtures.supportingLongFooter },
{ name: 'immersive-main', divider: 'supporting', fixture: pageDividerFixtures.supportingImmersiveLongMain },
{ name: 'immersive-panel', divider: 'side-nav', fixture: pageDividerFixtures.sideNavImmersiveLongFooter }
].forEach(test => {
it(test.name, async() => {
const elem = await fixture(test.fixture, { pagePadding: false, viewport: { width: 1000, height: 400 } });
await focusElem(getDivider(elem, test.divider));
window.scrollTo(0, document.body.scrollHeight);
const panel = elem.shadowRoot.querySelector(`.${test.divider}-panel`);
panel.scrollTop = panel.scrollHeight;
await expect(elem).to.be.golden({ margin: 0 });
});
});
});

describe('keyboard', () => {
// TO DO once arrow visuals added
});

describe('mouse', () => {
// TO DO
});

});