Skip to content

Commit

Permalink
Merge pull request #922 from Refinitiv/v6-tree-select-sequential-sele…
Browse files Browse the repository at this point in the history
…ction

v6 Tree & Tree Select sequential selection values
  • Loading branch information
Sakchai-Refinitiv authored Sep 15, 2023
2 parents f883987 + 576176c commit c5a5a17
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 66 deletions.
10 changes: 10 additions & 0 deletions documents/src/pages/utils/data-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,37 @@ interface DataItem extends CollectionItem {
* when the item is selected.
*/
value?: string;

/**
* Whether to show or hide
* the item from the renderer.
*/
hidden?: boolean;

/**
* Sets the item to be readonly.
* Read only items cannot be selected by a user.
*/
readonly?: boolean;

/**
* Sets the highlight state of the item.
* This is usually used for navigating over items,
* without affecting focus, or, highlighting a multiple selection.
*/
highlighted?: boolean;

/**
* Sets the selection state of the item.
* This is usually used for returning selected values.
*/
selected?: boolean;

/**
* Timestamp indicating the order of sequential selection
*/
selectedAt?: number;

/**
* Sets the item to be disabled.
* This completely prevents the
Expand Down
2 changes: 2 additions & 0 deletions packages/elements/src/combo-box/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,11 @@ export class ComboBox<T extends DataItem = ItemData> extends FormFieldElement {
* @returns {void}
*/
protected updateComposerValues(newValues: string[]): void {
const selectedAt = Date.now();
this.queryItems((item, composer): boolean => {
if (newValues.includes(composer.getItemPropertyValue(item, 'value') as string)) {
composer.setItemPropertyValue(item, 'selected', true);
composer.setItemPropertyValue(item, 'selectedAt', selectedAt + newValues.indexOf(item.value ?? '')); // Sequential selection
return true;
}
composer.setItemPropertyValue(item, 'selected', false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import '@formatjs/intl-pluralrules/polyfill.iife';
import '@refinitiv-ui/elements/tree-select';

import '@refinitiv-ui/elemental-theme/light/ef-tree-select';
import { aTimeout, elementUpdated, expect, fixture } from '@refinitiv-ui/test-helpers';
import { aTimeout, elementUpdated, expect, fixture, nextFrame } from '@refinitiv-ui/test-helpers';

import { flatData, flatSelection } from './mock_data/flat.js';
import { nestedData, nestedSelection, selectableCount } from './mock_data/nested.js';
import { changeItemSelection, checkMemo, doValuesMatch, openedUpdated } from './utils.js';
import { changeItemSelection, checkMemo, openedUpdated } from './utils.js';

describe('tree-select/Interaction', function () {
describe('Interaction Test', function () {
Expand All @@ -30,7 +30,7 @@ describe('tree-select/Interaction', function () {
el.save();
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
});

it('Persists a selection - nested', async function () {
Expand All @@ -46,7 +46,7 @@ describe('tree-select/Interaction', function () {
el.save();
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
});

it('Cancels a selection - flat', async function () {
Expand All @@ -66,14 +66,14 @@ describe('tree-select/Interaction', function () {
await elementUpdated(el);
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
expect(el.treeManager.visibleItems.length).to.equal(
flatData.length,
'Data list should remain the same'
);
const savedComposerValues = el.composerValues;
expect(doValuesMatch(savedValues, savedComposerValues)).to.equal(
true,
expect(savedValues).to.have.all.members(
savedComposerValues,
'Values and ComposerValues should be same'
);
expect(savedValues.length).to.equal(
Expand All @@ -92,11 +92,11 @@ describe('tree-select/Interaction', function () {
await elementUpdated(el);
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
expect(el.opened).to.equal(false, 'Cancel should close the list');
const savedComposerValues = el.composerValues;
expect(doValuesMatch(savedValues, savedComposerValues)).to.equal(
true,
expect(savedValues).to.have.all.members(
savedComposerValues,
'Values and ComposerValues should be same'
);
expect(savedValues.length).to.equal(
Expand All @@ -119,11 +119,11 @@ describe('tree-select/Interaction', function () {
const expectedSelection = data.filter((item) => item.selected).map((item) => item.value);
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
expect(el.opened).to.equal(false, 'Cancel should close the list');
const savedComposerValues = el.composerValues;
expect(doValuesMatch(savedValues, savedComposerValues)).to.equal(
true,
expect(savedValues).to.have.all.members(
savedComposerValues,
'Values and ComposerValues should be same'
);
expect(savedValues.length).to.equal(
Expand All @@ -140,11 +140,11 @@ describe('tree-select/Interaction', function () {
el.save();
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
// make change with no commit
changeItemSelection(el, flatSelection, true);
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
});

it('Persist a selection, make changes and cancel - nested', async function () {
Expand All @@ -155,20 +155,145 @@ describe('tree-select/Interaction', function () {
el.save();
const savedValues = el.values;
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
// make change with no commit
changeItemSelection(el, nestedSelection, true);
expect(savedValues.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(doValuesMatch(expectedSelection, savedValues)).to.equal(true, 'Values do not match');
expect(savedValues).to.have.all.members(expectedSelection, 'Values do not match');
});

it('Persists a selection - sequential selection', async function () {
const el = await fixture('<ef-tree-select opened lang="en-gb"></ef-tree-select>');
el.data = flatData;

// Check selected items
let expectedSelection = changeItemSelection(el, flatSelection);
await openedUpdated(el);
await nextFrame();

// Save and close popup
el.save();
el.opened = false;
await openedUpdated(el);

expect(el.values.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(el.values).to.have.ordered.members(
expectedSelection,
'Values sequential selection do not match'
);
});

it('Persists a selection - sequential selection modify', async function () {
const el = await fixture('<ef-tree-select opened lang="en-gb"></ef-tree-select>');
el.data = flatData;

// Check selected items
changeItemSelection(el, flatSelection);
await openedUpdated(el);
await nextFrame();

// Save and close popup
el.save();
el.opened = false;
await openedUpdated(el);

const expectedSelection = el.values;
const modifyTarget = flatSelection[0];

// Open popup and toggle an selected item which affect to sequential items
el.opened = true;
await openedUpdated(el);
el.treeManager.uncheckItem(modifyTarget);
await aTimeout(10);
el.treeManager.checkItem(modifyTarget);

// Save and close popup
el.save();
el.opened = false;
await openedUpdated(el);

// Modified item must always moved to the last selected
const modifiedItem = expectedSelection.shift();
expectedSelection.push(modifiedItem);

expect(el.values.length).to.equal(expectedSelection.length, 'Saved and Expected values are not equal');
expect(el.values).to.have.ordered.members(
expectedSelection,
'Values sequential selection do not match'
);
});

it('Cancels a selection - sequential selection', async function () {
const el = await fixture('<ef-tree-select show-pills lang="en-gb"></ef-tree-select>');
el.data = flatData;

// Check selected items
el.opened = true;
const expectedSelection = changeItemSelection(el, flatSelection);
await openedUpdated(el);
await nextFrame();

// Save and close popup
el.save();
el.opened = false;
await openedUpdated(el);

// Test selected items
expect(el.values.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(el.values).to.have.ordered.members(
expectedSelection,
'Values sequential selection do not match xxx'
);

// Test reverting values when cancel
el.treeManager.uncheckItem(flatSelection[1]);
await aTimeout(10);
el.treeManager.checkItem(flatSelection[1]);
el.cancel();
await openedUpdated(el);

el.opened = true;
await openedUpdated(el);
await nextFrame();

expect(el.values.length).to.equal(expectedSelection.length, 'Revert values are not equal');
expect(el.values).to.have.ordered.members(
expectedSelection,
'Revert values sequential selection do not match'
);

// Test reverting pill data correctly
let pillValues = el.pillsData.map((item) => item.value);
expect(pillValues.length).to.equal(
expectedSelection.length,
'Saved and values and pills values are not equal'
);
expect(pillValues).to.have.ordered.members(expectedSelection, 'Pill values do not match');
});

it('Adds selection to pills', async function () {
const el = await fixture('<ef-tree-select show-pills lang="en-gb"></ef-tree-select>');
el.data = flatData;

// Check selected items
changeItemSelection(el, flatSelection);
await openedUpdated(el);
await nextFrame();

// Save and close popup
el.save();
el.opened = false;
await openedUpdated(el);

// Open popup to get pillData
el.opened = true;
await openedUpdated(el);
await nextFrame();
const savedValues = el.values;
const pillValues = el.pillsData.map((item) => item.value);
expect(pillValues).to.deep.equal(el.values, 'Values do not match');

expect(pillValues.length).to.equal(savedValues.length, 'Saved and Expected pills are not equal');
expect(savedValues).to.have.ordered.members(pillValues, 'Values do not match');
});

it('Removes from selection on pill removal', async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import '@formatjs/intl-pluralrules/polyfill.iife';
import '@refinitiv-ui/elements/tree-select';

import '@refinitiv-ui/elemental-theme/light/ef-tree-select';
import { elementUpdated, expect, fixture } from '@refinitiv-ui/test-helpers';
import { aTimeout, elementUpdated, expect, fixture, nextFrame } from '@refinitiv-ui/test-helpers';

import { flatData, flatSelection } from './mock_data/flat.js';
import { openedUpdated } from './utils.js';

const data1 = [{ items: [{ selected: true, value: '1', label: '1' }] }];
const data2 = [
Expand All @@ -22,14 +25,53 @@ const data2 = [

describe('tree-select/Value', function () {
describe('Value Test', function () {
it('Value/values is empty by default', async function () {
const el = await fixture('<ef-tree-select lang="en-gb"></ef-tree-select>');
let el;

beforeEach(async function () {
el = await fixture('<ef-tree-select lang="en-gb"></ef-tree-select>');
});

it('Value/values is empty by default', function () {
expect(el.value).to.equal('', 'Value should be empty');
expect(el.values).to.be.empty;
});

it('Value/values property change', async function () {
el.data = flatData;
await elementUpdated(el);

// Single mode
let expectedValue = flatData[3].value;
el.value = expectedValue;
expect(el.value).to.equal(expectedValue, 'Value does not match');

// Multiple mode
let expectedValues = flatSelection.map((item) => item.value);
el.values = expectedValues;
await elementUpdated(el);
expect(el.values).to.have.ordered.members(expectedValues, 'Values do not match');

// Multiple mode set same values with a new sequence
expectedValues = flatSelection.map((item) => item.value).reverse();
el.values = expectedValues;
await elementUpdated(el);
expect(el.values).to.have.ordered.members(expectedValues, 'Values do not match with a new sequence');

// Pills sync with values correctly
el.showPills = true;
el.opened = true;
await openedUpdated(el);
await nextFrame();
el.updatePills();
let pillValues = el.pillsData.map((item) => item.value);
expect(pillValues.length).to.equal(
expectedValues.length,
'Saved and values and pills values are not equal'
);
expect(pillValues).to.have.ordered.members(expectedValues, 'Pill values do not match');
});

it('Value/values is accurate when data is set with selections', async function () {
const el = await fixture('<ef-tree-select lang="en-gb"></ef-tree-select>');
el.data = data2;
await elementUpdated(el);
expect(el.values).to.have.lengthOf(2);
Expand All @@ -40,7 +82,6 @@ describe('tree-select/Value', function () {
});

it('Values stay in sync with data changes', async function () {
const el = await fixture('<ef-tree-select lang="en-gb"></ef-tree-select>');
expect(el.values).to.deep.equal([]);
el.data = data1;
await elementUpdated(el);
Expand All @@ -52,6 +93,25 @@ describe('tree-select/Value', function () {
await elementUpdated(el);
expect(el.values).to.deep.equal([]);
});

it('Values sequential selection', async function () {
el.data = flatData;
let expectedSelection = [];

// Check selected items
for (const item of flatSelection) {
expectedSelection.push(item.value);
el.treeManager.checkItem(item);
await aTimeout(10); // Delay for sequential selection checking
}
el.save();

expect(el.values.length).to.equal(expectedSelection.length, 'Saved and Expected are not equal');
expect(expectedSelection).to.have.ordered.members(
el.values,
'Values sequential selection do not match'
);
});
});
describe('max', function () {
it('has correct disabled state on confirm button when values changed', async function () {
Expand Down
Loading

0 comments on commit c5a5a17

Please sign in to comment.