| // Copyright 2018 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import './index'; |
| import { assert } from 'chai'; |
| import { SelectSk } from './select-sk'; |
| import { setUpElementUnderTest } from '../../../infra-sk/modules/test_util'; |
| |
| describe('select-sk', () => { |
| const newInstance = setUpElementUnderTest<SelectSk>('select-sk'); |
| |
| interface InstantiationOptions { |
| disabled: boolean; |
| itemIds: string[]; |
| selectedItemId: string; |
| } |
| |
| const newInstanceWithOpts = async ( |
| opts: Partial<InstantiationOptions> |
| ): Promise<SelectSk> => { |
| const instance = newInstance((instance: SelectSk) => { |
| if (opts.disabled) instance.setAttribute('disabled', ''); |
| (opts.itemIds || []).forEach((id) => { |
| const item = document.createElement('div'); |
| item.id = id; |
| if (opts.selectedItemId && opts.selectedItemId === id) |
| item.setAttribute('selected', ''); |
| instance.appendChild(item); |
| }); |
| }); |
| await Promise.resolve(); // Give the mutation observer a chance to fire. |
| return instance; |
| }; |
| |
| describe('selection property', () => { |
| it('has a default value', () => { |
| const selectSk = newInstance(); |
| assert.equal(-1, (selectSk as any)._selection); |
| assert.equal(-1, selectSk.selection); |
| }); |
| |
| it('changes based on children', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['a', 'b'], |
| selectedItemId: 'b', |
| }); |
| assert.equal(1, (selectSk as any)._selection); |
| assert.equal(1, selectSk.selection); |
| }); |
| |
| it('can go back to -1', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['a', 'b'], |
| selectedItemId: 'b', |
| }); |
| selectSk.selection = -1; |
| assert.equal(-1, selectSk.selection); |
| assert.isFalse(selectSk.querySelector('#b')!.hasAttribute('selected')); |
| }); |
| |
| it('parses strings', async () => { |
| const selectSk = await newInstanceWithOpts({ itemIds: ['a', 'b'] }); |
| selectSk.selection = '1'; |
| assert.equal(1, +selectSk.selection); |
| assert.isTrue(selectSk.querySelector('#b')!.hasAttribute('selected')); |
| }); |
| |
| it('treats null and undefined and out of range as -1', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['a', 'b'], |
| selectedItemId: 'b', |
| }); |
| selectSk.selection = null; |
| assert.equal(-1, selectSk.selection); |
| assert.isFalse(selectSk.querySelector('#b')!.hasAttribute('selected')); |
| |
| selectSk.selection = 0; |
| assert.equal(0, selectSk.selection); |
| assert.isTrue(selectSk.querySelector('#a')!.hasAttribute('selected')); |
| |
| selectSk.selection = undefined; |
| assert.equal(-1, selectSk.selection); |
| |
| selectSk.selection = 10; |
| assert.equal(-1, selectSk.selection); |
| |
| selectSk.selection = -3; |
| assert.equal(-1, selectSk.selection); |
| }); |
| |
| it('changes selected attributes on children', async () => { |
| const selectSk = await newInstanceWithOpts({ itemIds: ['a', 'b'] }); |
| const a = selectSk.querySelector('#a')!; |
| const b = selectSk.querySelector('#b')!; |
| selectSk.selection = 0; |
| assert.equal(0, selectSk.selection); |
| assert.isTrue(a.hasAttribute('selected')); |
| assert.isFalse(b.hasAttribute('selected')); |
| selectSk.selection = 1; |
| assert.equal(1, selectSk.selection); |
| assert.isFalse(a.hasAttribute('selected')); |
| assert.isTrue(b.hasAttribute('selected')); |
| }); |
| |
| it('stays fixed when disabled', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['a', 'b'], |
| selectedItemId: 'b', |
| }); |
| assert.equal(1, (selectSk as any)._selection); |
| assert.equal(1, selectSk.selection); |
| assert.equal('0', selectSk.getAttribute('tabindex')); |
| selectSk.disabled = true; |
| selectSk.selection = 0; |
| assert.equal(1, (selectSk as any)._selection); |
| assert.equal(1, selectSk.selection); |
| assert.equal(false, selectSk.hasAttribute('tabindex')); |
| }); |
| |
| it('gets updated when select-sk is re-enabled', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| disabled: true, |
| itemIds: ['a', 'b'], |
| selectedItemId: 'b', |
| }); |
| assert.equal(-1, (selectSk as any)._selection); |
| assert.equal(-1, selectSk.selection); |
| selectSk.disabled = false; |
| assert.equal(1, (selectSk as any)._selection); |
| assert.equal(1, selectSk.selection); |
| assert.isFalse(selectSk.hasAttribute('disabled')); |
| }); |
| }); // end describe('selected property') |
| |
| describe('click', () => { |
| it('changes selection', async () => { |
| const selectSk = await newInstanceWithOpts({ itemIds: ['a', 'b'] }); |
| assert.equal('listbox', selectSk.getAttribute('role')); |
| const a = selectSk.querySelector<HTMLDivElement>('#a')!; |
| const b = selectSk.querySelector<HTMLDivElement>('#b')!; |
| a.click(); |
| assert.equal(0, selectSk.selection); |
| assert.isTrue(a.hasAttribute('selected')); |
| assert.equal('true', a.getAttribute('aria-selected')); |
| assert.equal('option', a.getAttribute('role')); |
| assert.isFalse(b.hasAttribute('selected')); |
| assert.equal('false', b.getAttribute('aria-selected')); |
| assert.equal('option', b.getAttribute('role')); |
| b.click(); |
| assert.equal(1, selectSk.selection); |
| assert.isFalse(a.hasAttribute('selected')); |
| assert.equal('false', a.getAttribute('aria-selected')); |
| assert.isTrue(b.hasAttribute('selected')); |
| assert.equal('true', b.getAttribute('aria-selected')); |
| }); |
| |
| it('ignores clicks when disabled', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| disabled: true, |
| itemIds: ['a', 'b'], |
| }); |
| const a = selectSk.querySelector<HTMLDivElement>('#a')!; |
| const b = selectSk.querySelector<HTMLDivElement>('#b')!; |
| a.click(); |
| assert.equal(-1, selectSk.selection); |
| assert.isFalse(a.hasAttribute('selected')); |
| assert.isFalse(b.hasAttribute('selected')); |
| b.click(); |
| assert.equal(-1, selectSk.selection); |
| assert.isFalse(a.hasAttribute('selected')); |
| assert.isFalse(b.hasAttribute('selected')); |
| }); |
| }); // end describe('click') |
| |
| describe('inserting new children', () => { |
| it('updates selection property', async () => { |
| const selectSk = await newInstanceWithOpts({ itemIds: ['', '', ''] }); |
| assert.equal(-1, selectSk.selection); |
| let div = document.createElement('div'); |
| div.setAttribute('selected', ''); |
| selectSk.appendChild(div); |
| div = document.createElement('div'); |
| selectSk.appendChild(div); |
| // Need to do the check post microtask so the mutation observer gets a |
| // chance to fire. |
| await Promise.resolve(); |
| assert.equal(3, selectSk.selection); |
| }); |
| |
| it('does not check children when disabled', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| disabled: true, |
| itemIds: ['', '', ''], |
| }); |
| assert.equal(-1, selectSk.selection); |
| let div = document.createElement('div'); |
| div.setAttribute('selected', ''); |
| selectSk.appendChild(div); |
| div = document.createElement('div'); |
| selectSk.appendChild(div); |
| // Need to do the check post microtask so the mutation observer gets a |
| // chance to fire. |
| await Promise.resolve(); |
| assert.equal(-1, selectSk.selection); |
| }); |
| }); // end describe('inserting new children') |
| |
| describe('mutation of child selected attribute', () => { |
| it('does update selection', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['', 'd1', 'd2'], |
| selectedItemId: 'd2', |
| }); |
| assert.equal(2, selectSk.selection); |
| selectSk.querySelector('#d2')!.removeAttribute('selected'); |
| selectSk.querySelector('#d1')!.setAttribute('selected', ''); |
| // Need to do the check post microtask so the mutation observer gets a |
| // chance to fire. |
| await Promise.resolve(); |
| assert.equal(1, selectSk.selection); |
| }); |
| }); // end describe('mutation of child selected attribute' |
| |
| describe('keyboard navigation', () => { |
| it('follows arrow keys', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['', '', 'd2'], |
| selectedItemId: 'd2', |
| }); |
| assert.equal(2, selectSk.selection); |
| (selectSk as any)._onKeyDown( |
| new KeyboardEvent('keydown', { key: 'ArrowUp' }) |
| ); |
| assert.equal(1, selectSk.selection); |
| (selectSk as any)._onKeyDown( |
| new KeyboardEvent('keydown', { key: 'ArrowDown' }) |
| ); |
| assert.equal(2, selectSk.selection); |
| (selectSk as any)._onKeyDown( |
| new KeyboardEvent('keydown', { key: 'Home' }) |
| ); |
| assert.equal(0, selectSk.selection); |
| (selectSk as any)._onKeyDown( |
| new KeyboardEvent('keydown', { key: 'End' }) |
| ); |
| assert.equal(2, selectSk.selection); |
| // Don't wrap around. |
| (selectSk as any)._onKeyDown( |
| new KeyboardEvent('keydown', { key: 'ArrowDown' }) |
| ); |
| assert.equal(2, selectSk.selection); |
| }); |
| }); // end describe('keyboard navigation') |
| |
| describe('focus', () => { |
| it('drops focus when disabled', async () => { |
| const selectSk = await newInstanceWithOpts({ |
| itemIds: ['', '', 'd2'], |
| selectedItemId: 'd2', |
| }); |
| selectSk.focus(); |
| assert.equal(selectSk, document.activeElement); |
| selectSk.disabled = true; |
| assert.notEqual(selectSk, document.activeElement); |
| }); |
| }); // end describe('focus') |
| }); |