2020-07-16 20:13:57 +00:00
|
|
|
/* eslint-disable jest/no-hooks */
|
|
|
|
// License: LGPL-3.0-or-later
|
|
|
|
import { renderHook, act } from '@testing-library/react-hooks';
|
|
|
|
import useSteps, { KeyedStep, KeyedStepMap } from './useSteps';
|
|
|
|
import fromPairs from 'lodash/fromPairs';
|
|
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
|
const stepActions = { addStep: () => { }, removeStep: () => { } };
|
|
|
|
|
|
|
|
describe('.next', () => {
|
|
|
|
|
|
|
|
it('nothing changes if there is no next', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.next();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('nothing changes if next is disabled', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, {
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': false, '2': true },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.next();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('go to next if next exists', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.next();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(1);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('.back', () => {
|
|
|
|
|
|
|
|
it('nothing changes if there is no back', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.back();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('nothing changes if back is disabled', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }, { key: 'last' }], ...stepActions }, {
|
|
|
|
activeStep: 2,
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': true, '2': true, 'last': false },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.back();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(2);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('if back is the first item, we ignore its disabled state', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }, { key: 'last' }], ...stepActions }, {
|
|
|
|
activeStep: 1,
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': true, '2': false, 'last': false },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.back();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('go to back if back exists', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, { activeStep: 1 }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.back();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('.goto', () => {
|
|
|
|
|
|
|
|
it('nothing changes if goto value is below 0', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.goto(-1);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('nothing changes if goto value is bigger number of steps', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.goto(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('nothing changes if goto value is disabled', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, {
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': false, '2': true },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.goto(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('if goto step is 0, we always go back regardless of disabledness', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, {
|
|
|
|
activeStep: 1,
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': false, '2': true },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.goto(0);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('goto step if it exists', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, { activeStep: 1 }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.goto(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(1);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('.first', () => {
|
|
|
|
|
|
|
|
it('changes nothing if already first', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.first();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('if first is disabled we still go back', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, {
|
|
|
|
activeStep: 1,
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': true, '2': false },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.first();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('go to first', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, { activeStep: 1 }));
|
|
|
|
const original = result.current;
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
result.current.first();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('.last', () => {
|
|
|
|
it('changes nothing if already last', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, { activeStep: 1 }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.last();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(1);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('changes nothing if last is disabled', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }, {
|
2020-09-30 17:05:22 +00:00
|
|
|
disabled: { 'i': false, '2': true },
|
2020-07-16 20:13:57 +00:00
|
|
|
}));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.last();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(0);
|
|
|
|
expect(Object.is(result.current, original)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('go to last', async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const { result } = renderHook(() => useSteps({ steps: [{ key: 'i' }, { key: '2' }], ...stepActions }));
|
|
|
|
const original = result.current;
|
|
|
|
act(() => {
|
|
|
|
result.current.last();
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.current.activeStep).toBe(1);
|
|
|
|
expect(Object.is(result.current, original)).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('.disable', () => {
|
|
|
|
describe.each([
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 1, { i: false, 2: false }, 1, 0, { i: false, 2: true }],
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 0, { i: false, 2: false }, 1, 0, { i: false, 2: true }],
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 1, { i: true, 2: false }, 1, 0, { i: true, 2: true }],
|
2020-09-30 17:05:22 +00:00
|
|
|
[[{ key: 'i' }, { key: '2' }], 0, { i: false, 2: false }, 0, 0, { i: true, 2: false }],
|
2020-07-16 20:13:57 +00:00
|
|
|
])('.disable with keys of %j, activeStep: %d, initial disabled: %j, we disabled index %d', (
|
|
|
|
steps,
|
|
|
|
initialActiveStep,
|
|
|
|
initialDisabled,
|
|
|
|
indexToDisable,
|
|
|
|
activeStep,
|
|
|
|
disabled) => {
|
2020-10-21 17:59:27 +00:00
|
|
|
let current: ReturnType<typeof useSteps> = null;
|
2020-07-16 20:13:57 +00:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
const { result } = renderHook(() => useSteps({ steps, ...stepActions },
|
|
|
|
{ activeStep: initialActiveStep, disabled: initialDisabled }));
|
|
|
|
act(() => {
|
|
|
|
result.current.disable(indexToDisable);
|
|
|
|
});
|
|
|
|
current = result.current;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it(`sets activeStep to ${activeStep}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(current.activeStep).toBe(activeStep);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it(`sets disabled to properly`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(current.disabled).toStrictEqual(disabled);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('.enable', () => {
|
|
|
|
describe.each([
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 1, { i: false, 2: false }, 1, 1, { i: false, 2: false }],
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 0, { i: false, 2: true }, 1, 0, { i: false, 2: false }],
|
|
|
|
[[{ key: 'i' }, { key: '2' }], 1, { i: true, 2: false }, 1, 1, { i: true, 2: false }],
|
2020-09-30 17:05:22 +00:00
|
|
|
[[{ key: 'i' }, { key: '2' }], 0, { i: true, 2: false }, 0, 0, { i: false, 2: false }],
|
2020-07-16 20:13:57 +00:00
|
|
|
])('.enable with keys of %j, activeStep: %d, initial enabled: %j, we enable index %d', (
|
|
|
|
steps,
|
|
|
|
initialActiveStep,
|
|
|
|
initialDisabled,
|
|
|
|
indexToEnable,
|
|
|
|
activeStep,
|
|
|
|
disabled) => {
|
2020-10-21 17:59:27 +00:00
|
|
|
let current: ReturnType<typeof useSteps> = null;
|
2020-07-16 20:13:57 +00:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
const { result } = renderHook(() => useSteps({ steps, ...stepActions },
|
|
|
|
{ activeStep: initialActiveStep, disabled: initialDisabled }));
|
|
|
|
act(() => {
|
|
|
|
result.current.enable(indexToEnable);
|
|
|
|
});
|
|
|
|
current = result.current;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it(`sets activeStep to ${activeStep}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(current.activeStep).toBe(activeStep);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it(`sets disabled to properly`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(current.disabled).toStrictEqual(disabled);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('modify steps', () => {
|
|
|
|
function createTableEntry(props: {
|
|
|
|
initial: {
|
|
|
|
steps: KeyedStep[],
|
|
|
|
activeStep?: number,
|
|
|
|
disabled?: KeyedStepMap<boolean>,
|
|
|
|
completed?:
|
|
|
|
KeyedStepMap<boolean>
|
|
|
|
},
|
|
|
|
stepChange: KeyedStep[],
|
|
|
|
expectation: {
|
|
|
|
activeStep?: number,
|
|
|
|
disabled?: KeyedStepMap<boolean>,
|
|
|
|
completed?: KeyedStepMap<boolean>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
): [
|
|
|
|
KeyedStep[], number | undefined, KeyedStepMap<boolean> | undefined, KeyedStepMap<boolean> | undefined, // initial
|
|
|
|
KeyedStep[], //stepChange
|
|
|
|
number | undefined, KeyedStepMap<boolean>, KeyedStepMap<boolean> //expectations
|
|
|
|
] {
|
|
|
|
const expectation = {
|
|
|
|
activeStep: props.initial.activeStep || 0,
|
|
|
|
disabled: props.initial.disabled || fromPairs(props.initial.steps.map(i => [i.key, false])),
|
|
|
|
completed: props.initial.completed || fromPairs(props.initial.steps.map(i => [i.key, false])),
|
2020-09-30 17:05:22 +00:00
|
|
|
...props.expectation,
|
2020-07-16 20:13:57 +00:00
|
|
|
};
|
|
|
|
return [
|
|
|
|
props.initial.steps, props.initial.activeStep, props.initial.disabled, props.initial.completed,
|
|
|
|
props.stepChange,
|
2020-09-30 17:05:22 +00:00
|
|
|
expectation.activeStep, expectation.disabled, expectation.completed,
|
2020-07-16 20:13:57 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
describe.each([
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }] },
|
|
|
|
stepChange: [{ key: 'i' }, { key: '2' }],
|
|
|
|
expectation: {
|
2020-09-30 17:05:22 +00:00
|
|
|
},
|
2020-07-16 20:13:57 +00:00
|
|
|
}),
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }], activeStep: 0 },
|
|
|
|
stepChange: [ { key: '2' }],
|
|
|
|
expectation: {
|
|
|
|
disabled: {2: false},
|
2020-09-30 17:05:22 +00:00
|
|
|
completed: {2: false},
|
|
|
|
},
|
2020-07-16 20:13:57 +00:00
|
|
|
}),
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }], activeStep: 1 },
|
|
|
|
stepChange: [ { key: 'i' }],
|
|
|
|
expectation: {
|
|
|
|
activeStep: 0,
|
|
|
|
disabled: {i: false},
|
2020-09-30 17:05:22 +00:00
|
|
|
completed: {i: false},
|
|
|
|
},
|
2020-07-16 20:13:57 +00:00
|
|
|
}),
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }, {key: 'last'}], activeStep: 2, disabled: {i: false, 2: true, last:false}},
|
|
|
|
stepChange: [ { key: 'i' }, { key: '2' }],
|
|
|
|
expectation: {
|
|
|
|
activeStep: 0,
|
|
|
|
disabled: {i: false, 2: true},
|
2020-09-30 17:05:22 +00:00
|
|
|
completed: {i: false, 2: false},
|
|
|
|
},
|
2020-07-16 20:13:57 +00:00
|
|
|
}),
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }, {key: 'last'}], activeStep: 2, disabled: {i: false, 2: true, last:false}},
|
|
|
|
stepChange: [ { key: 'i' }, {key: 'last'}, { key: '2' }],
|
|
|
|
expectation: {
|
|
|
|
activeStep: 1,
|
|
|
|
disabled: {i: false, 2: true, last:false},
|
2020-09-30 17:05:22 +00:00
|
|
|
completed: {i: false, 2: false, last: false},
|
|
|
|
},
|
2020-07-16 20:13:57 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
createTableEntry({
|
|
|
|
initial: { steps: [{ key: 'i' }, { key: '2' }, {key: 'last'}], activeStep: 2, disabled: {i: false, 2: true, last:false}},
|
2020-09-30 17:05:22 +00:00
|
|
|
stepChange: [ {key: 'last'},{ key: 'i' }],
|
2020-07-16 20:13:57 +00:00
|
|
|
expectation: {
|
|
|
|
activeStep: 0,
|
|
|
|
disabled: {i: false, last:false},
|
2020-09-30 17:05:22 +00:00
|
|
|
completed: {i: false, last: false},
|
|
|
|
},
|
|
|
|
}),
|
2020-07-16 20:13:57 +00:00
|
|
|
])('with initial steps %j, active: %d, disabled: %o, completed: %o and change steps to %j', (
|
|
|
|
initialSteps, initialActiveStep, initialDisabled, initialCompleted,
|
|
|
|
stepChange,
|
|
|
|
expectationActiveStep, expectationDisabled, expectationCompleted
|
|
|
|
) => {
|
|
|
|
let finalState:ReturnType<typeof useSteps>;
|
|
|
|
beforeEach(() => {
|
|
|
|
const { result, rerender } = renderHook((steps) => useSteps({ steps, ...stepActions },
|
|
|
|
{ activeStep: initialActiveStep, disabled: initialDisabled, completed: initialCompleted }), {initialProps: initialSteps});
|
|
|
|
rerender(stepChange);
|
|
|
|
finalState = result.current;
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`has the correct steps of ${JSON.stringify(stepChange)}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(finalState.steps).toStrictEqual(stepChange);
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`has an activeStep of ${expectationActiveStep}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(finalState.activeStep).toBe(expectationActiveStep);
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`has an disabled list of ${JSON.stringify(expectationDisabled)}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(finalState.disabled).toStrictEqual(expectationDisabled);
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`has completed list of ${JSON.stringify(expectationCompleted)}`, () => {
|
|
|
|
expect.hasAssertions();
|
|
|
|
expect(finalState.completed).toStrictEqual(expectationCompleted);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|