devextreme-vue
Version:
DevExtreme Vue UI and Visualization Components
560 lines (558 loc) • 22.7 kB
JavaScript
/*!
* devextreme-vue
* Version: 25.1.6
* Build date: Mon Oct 13 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file in the root of the project for details.
*
* https://github.com/DevExpress/devextreme-vue
*/
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const vue_helper_1 = require("../vue-helper");
const configuration_1 = __importStar(require("../configuration"));
function createRootConfig(updateFunc) {
return new configuration_1.default(updateFunc, null, {});
}
function createConfigWithExpectedChildren(children) {
return new configuration_1.default(jest.fn(), null, {}, children);
}
describe('fullPath building', () => {
const testCases = [
{
msg: 'works for null',
name: null,
expected: null,
},
{
msg: 'works without owner',
name: 'abc',
expected: 'abc',
},
{
msg: 'works with owner',
name: 'abc',
ownerPath: 'def',
expected: 'def.abc',
},
{
msg: 'works for collection item',
name: 'abc',
collectionIndex: 123,
expected: 'abc[123]',
},
{
msg: 'works for collection item with owner',
name: 'abc',
ownerPath: 'def',
collectionIndex: 123,
expected: 'def.abc[123]',
},
];
for (const { msg, name, collectionIndex, ownerPath, expected, } of testCases) {
it(msg, () => {
const isCollection = collectionIndex !== undefined;
const ownerConfig = ownerPath ? { fullPath: ownerPath } : undefined;
expect(new configuration_1.default(jest.fn(), name, {}, undefined, isCollection, collectionIndex, ownerConfig).fullPath).toBe(expected);
});
}
});
it('calls update from nested', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
const nested = root.createNested('option', {});
nested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option.prop', 123);
});
it('calls update from subnested', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
const nested = root.createNested('option', {});
const subNested = nested.createNested('subOption', {});
subNested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option.subOption.prop', 123);
});
it('calls update from nested collectionItem (first)', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
const nested = root.createNested('option', {}, true);
root.createNested('option', {}, true);
nested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option[0].prop', 123);
});
it('calls update from nested collectionItem (middle)', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
root.createNested('option', {}, true);
const nested = root.createNested('option', {}, true);
root.createNested('option', {}, true);
nested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option[1].prop', 123);
});
it('calls update from nested collectionItem (last)', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
root.createNested('option', {}, true);
root.createNested('option', {}, true);
const nested = root.createNested('option', {}, true);
nested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option[2].prop', 123);
});
it('calls update from nested collectionItem (the only)', () => {
const callback = jest.fn();
const root = createRootConfig(callback);
const nested = root.createNested('option', {}, true);
nested.updateValue('prop', 123);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('option[0].prop', 123);
});
it('binds option watchers', () => {
const updateValueFunc = jest.fn();
const $watchFunc = jest.fn();
const innerChanges = {};
(0, configuration_1.bindOptionWatchers)({
updateValue: updateValueFunc,
getOptionsToWatch: () => ['prop1'],
innerChanges: {},
}, {
$watch: $watchFunc,
}, innerChanges);
expect($watchFunc.mock.calls[0][0]).toBe('prop1');
const value = {};
$watchFunc.mock.calls[0][1](value);
expect(updateValueFunc).toHaveBeenCalledTimes(1);
expect(updateValueFunc.mock.calls[0][0]).toBe('prop1');
expect(updateValueFunc.mock.calls[0][1]).toBe(value);
});
it('should update only when raw value not equal', () => {
const updateValueFunc = jest.fn();
const $watchFunc = jest.fn();
const innerChanges = { prop1: 'test' };
(0, configuration_1.bindOptionWatchers)({
updateValue: updateValueFunc,
getOptionsToWatch: () => ['prop1'],
innerChanges: {},
}, {
$watch: $watchFunc,
}, innerChanges);
$watchFunc.mock.calls[0][1]((0, vue_1.reactive)(innerChanges).prop1);
expect(updateValueFunc).toHaveBeenCalledTimes(0);
$watchFunc.mock.calls[0][1]((0, vue_1.reactive)({ prop1: 'test1' }).prop1);
expect(updateValueFunc).toHaveBeenCalledTimes(1);
expect(updateValueFunc.mock.calls[0][0]).toBe('prop1');
expect(updateValueFunc.mock.calls[0][1]).toBe('test1');
});
describe('initial configuration', () => {
it('pulls value from nested', () => {
const root = createRootConfig(jest.fn());
const nested = root.createNested('option', {});
nested
.createNested('subOption', { prop: 123 })
.init(['prop']);
expect(root.getNestedOptionValues()).toMatchObject({
option: {
subOption: {
prop: 123,
},
},
});
});
it('pulls array of values from a coollectionItem nested (single value)', () => {
const root = createRootConfig(jest.fn());
root.createNested('options', { propA: 123 }, true);
expect(root.getNestedOptionValues()).toMatchObject({
options: [
{ propA: 123 },
],
});
});
it('pulls array of values from a coollectionItem nested (several values)', () => {
const root = createRootConfig(jest.fn());
root.createNested('options', { propA: 123 }, true);
root.createNested('options', { propA: 456, propB: 789 }, true);
expect(root.getNestedOptionValues()).toMatchObject({
options: [
{ propA: 123 },
{ propA: 456, propB: 789 },
],
});
});
it('pulls values from the last nested (not a coollectionItem)', () => {
const root = createRootConfig(jest.fn());
root.createNested('option', { propA: 123 });
root.createNested('option', { propA: 456, propB: 789 });
expect(root.getNestedOptionValues()).toMatchObject({
option: { propA: 456, propB: 789 },
});
});
it('pulls values from self and nested', () => {
const root = new configuration_1.default(jest.fn(), null, { propA: 123 });
const nested = root.createNested('option', { propB: 456 });
nested.createNested('subOption', { propC: 789 });
expect(root.getNestedOptionValues()).toMatchObject({
option: {
propB: 456,
subOption: {
propC: 789,
},
},
});
expect(root.initialValues).toMatchObject({
propA: 123,
});
});
it('pulls empty value for correct option structure T728446', () => {
const root = createRootConfig(jest.fn());
const nested = root.createNested('option', {}, true);
nested.createNested('subOption', {});
expect(root.getNestedOptionValues()).toMatchObject({ option: [{ subOption: {} }] });
});
it('pulls values and ignores empty nested', () => {
const root = createRootConfig(jest.fn());
const nested = root.createNested('option', {});
nested.init(['empty']);
nested
.createNested('subOption', { prop: 123 })
.init(['prop']);
root.createNested('anotherOption', {});
nested.createNested('anotherSubOption', {});
expect(root.getNestedOptionValues()).toMatchObject({
option: {
subOption: {
prop: 123,
},
},
});
});
});
describe('collection items creation', () => {
describe('not-expected item .isCollectionItem prop', () => {
it('is true if isCollection arg is true', () => {
const owner = new configuration_1.default(jest.fn(), null, {});
const nested = owner.createNested('name', {}, true);
expect(nested.isCollectionItem).toBeTruthy();
});
it('is false if isCollection arg is false', () => {
const owner = new configuration_1.default(jest.fn(), null, {});
const nested = owner.createNested('name', {}, false);
expect(nested.isCollectionItem).toBeFalsy();
});
it('is false if isCollection arg is undefined', () => {
const owner = new configuration_1.default(jest.fn(), null, {});
const nested = owner.createNested('name', {});
expect(nested.isCollectionItem).toBeFalsy();
});
});
describe('expectation of collection item', () => {
it('applied if isCollection arg is true', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: true, optionName: 'def' } });
const nested = owner.createNested('abc', {}, true);
expect(nested.isCollectionItem).toBeTruthy();
expect(nested.name).toBe('def');
});
it('applied if isCollection arg is false', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: true, optionName: 'def' } });
const nested = owner.createNested('abc', {}, false);
expect(nested.isCollectionItem).toBeTruthy();
expect(nested.name).toBe('def');
});
it('applied if isCollection arg is undefined', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: true, optionName: 'def' } });
const nested = owner.createNested('abc', {});
expect(nested.isCollectionItem).toBeTruthy();
expect(nested.name).toBe('def');
});
});
describe('expected as collection item .isCollectionItem prop', () => {
it('is true if isCollection arg is true', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: false, optionName: 'def' } });
const nested = owner.createNested('abc', {}, true);
expect(nested.isCollectionItem).toBeFalsy();
});
it('is true if isCollection arg is false', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: false, optionName: 'def' } });
const nested = owner.createNested('abc', {}, false);
expect(nested.isCollectionItem).toBeFalsy();
});
it('is true if isCollection arg is undefined', () => {
const owner = createConfigWithExpectedChildren({ abc: { isCollectionItem: false, optionName: 'def' } });
const nested = owner.createNested('abc', {});
expect(nested.isCollectionItem).toBeFalsy();
expect(nested.name).toBe('def');
});
});
});
describe('options watch-list', () => {
it('includes option with initial values', () => {
const config = new configuration_1.default(jest.fn(), null, { option1: 123, option2: 456 });
config.init(['option1']);
expect(config.getOptionsToWatch()).toEqual(['option1']);
});
it('includes option without initial values', () => {
const config = new configuration_1.default(jest.fn(), null, {});
config.init(['option1']);
expect(config.getOptionsToWatch()).toEqual(['option1']);
});
it('excludes option if finds nested config with the same name', () => {
const config = new configuration_1.default(jest.fn(), null, {});
config.init(['option1', 'theNestedOption']);
config.createNested('theNestedOption', {});
expect(config.getOptionsToWatch()).toEqual(['option1']);
});
});
describe('onOptionChanged', () => {
[
{
fullName: 'option',
value: 'new value',
previousValue: 'old value',
component: null,
},
{
fullName: 'option.nestedOption.subNestedOption',
value: 'any value',
previousValue: 'old value',
component: { option: (name) => name === 'option' && 'new value' },
},
{
fullName: 'option[0]',
value: 'any value',
previousValue: 'old value',
component: { option: (name) => name === 'option' && 'new value' },
},
{
fullName: 'option[0].nestedOption',
value: 'any value',
previousValue: 'old value',
component: { option: (name) => name === 'option' && 'new value' },
},
].map((optionChangedArgs) => {
it('emits from root configuration', () => {
const innerChanges = {};
const emitStub = jest.fn();
const config = new configuration_1.default(jest.fn(), null, {});
const component = {
$emit: emitStub,
$props: { option: undefined },
$options: {
props: {
option: undefined,
},
},
};
(0, configuration_1.setEmitOptionChangedFunc)(config, component, innerChanges);
config.onOptionChanged(optionChangedArgs);
expect(emitStub).toHaveBeenCalledTimes(1);
expect(emitStub).toHaveBeenCalledWith('update:option', 'new value');
expect(innerChanges).toEqual({ option: 'new value' });
});
});
[
{
fullName: 'option.nestedOption',
value: 'new value',
previousValue: 'old value',
component: null,
},
{
fullName: 'option.nestedOption.subNestedOption',
value: 'any value',
previousValue: 'old value',
component: { option: (name) => name === 'option.nestedOption' && 'new value' },
},
].map((optionChangedArgs) => {
it('emits from nested configuration', () => {
const innerChanges = {};
const emitStub = jest.fn();
const config = new configuration_1.default(jest.fn(), null, {});
const nestedConfig = config.createNested('option', {});
const component = {
$emit: emitStub,
$props: { nestedOption: undefined },
$options: {
props: {
nestedOption: undefined,
},
},
};
(0, configuration_1.setEmitOptionChangedFunc)(nestedConfig, component, innerChanges);
config.onOptionChanged(optionChangedArgs);
expect(emitStub).toHaveBeenCalledTimes(1);
expect(emitStub).toHaveBeenCalledWith('update:nestedOption', 'new value');
expect(innerChanges).toEqual({ nestedOption: 'new value' });
});
});
[
{
fullName: 'option[0].nestedOption',
value: 'new value',
previousValue: 'old value',
component: null,
},
{
fullName: 'option[0].nestedOption.subNestedOption',
value: 'any value',
previousValue: 'old value',
component: { option: (name) => name === 'option[0].nestedOption' && 'new value' },
},
].map((optionChangedArgs) => {
it('emits from nested collection configuration', () => {
const innerChanges = {};
const emitStub = jest.fn();
const config = new configuration_1.default(jest.fn(), null, {});
const nestedConfig = config.createNested('option', {}, true);
const component = {
$emit: emitStub,
$props: { nestedOption: undefined },
$options: {
props: {
nestedOption: undefined,
},
},
};
(0, configuration_1.setEmitOptionChangedFunc)(nestedConfig, component, innerChanges);
config.onOptionChanged(optionChangedArgs);
expect(emitStub).toHaveBeenCalledTimes(1);
expect(emitStub).toHaveBeenCalledWith('update:nestedOption', 'new value');
expect(innerChanges).toEqual({ nestedOption: 'new value' });
});
});
[
{
fullName: 'option',
value: 'value',
previousValue: 'value',
component: null,
},
{
fullName: 'option',
value: [],
previousValue: [],
component: null,
},
].map((optionChangedArgs) => {
it('does not emit', () => {
const innerChanges = {};
const emitStub = jest.fn();
const config = new configuration_1.default(jest.fn(), null, {});
const component = {
$: {},
$emit: emitStub,
$props: {},
$options: {},
};
(0, configuration_1.setEmitOptionChangedFunc)(config, component, innerChanges);
config.onOptionChanged(optionChangedArgs);
expect(emitStub).toHaveBeenCalledTimes(0);
});
});
// https://github.com/DevExpress/devextreme-vue/issues/330
it('emits once', () => {
const emitStubRoot = jest.fn();
const emitStubNested = jest.fn();
const component = {
$: {},
$emit: emitStubRoot,
$props: { option: undefined },
$options: {
props: {
option: undefined,
},
},
};
const config = new configuration_1.default(jest.fn(), null, {});
(0, configuration_1.setEmitOptionChangedFunc)(config, component, {});
const nestedConfig1 = config.createNested('option', {}, true);
(0, configuration_1.setEmitOptionChangedFunc)(nestedConfig1, component, {});
const subNestedConfig = nestedConfig1.createNested('option', {}, false);
(0, configuration_1.setEmitOptionChangedFunc)(subNestedConfig, component, {});
const nestedConfig2 = config.createNested('option', {}, true);
(0, configuration_1.setEmitOptionChangedFunc)(nestedConfig2, component, {});
config.onOptionChanged({
fullName: 'option', value: 'new value', previousValue: 'old value', component: null,
});
expect(emitStubRoot).toHaveBeenCalledTimes(1);
expect(emitStubNested).toHaveBeenCalledTimes(0);
});
it('shouldn\'t emit if component does\'t have the prop', () => {
const emitStubRoot = jest.fn();
const config = new configuration_1.default(jest.fn(), null, {});
const component = {
$emit: emitStubRoot,
$props: { option: undefined },
$options: {
props: {
option: undefined,
},
},
};
(0, configuration_1.setEmitOptionChangedFunc)(config, component, {});
config.onOptionChanged({
fullName: 'wrongName',
value: 'new value',
previousValue: 'old value',
component: null,
});
expect(emitStubRoot).toHaveBeenCalledTimes(0);
});
it('should use v-model event name and mutate innerChanges with VMODEL_NAME when option is "value" and v-model is active', () => {
const innerChanges = {};
const config = new configuration_1.default(jest.fn(), null, {});
const emitStub = jest.fn();
const component = {
$emit: emitStub,
$props: { value: false, [vue_helper_1.VMODEL_NAME]: false },
$options: {
props: { value: false, [vue_helper_1.VMODEL_NAME]: false },
model: true,
},
$: {
vnode: {
props: { value: false, [vue_helper_1.VMODEL_NAME]: false },
},
},
};
(0, configuration_1.setEmitOptionChangedFunc)(config, component, innerChanges);
config.onOptionChanged({
fullName: 'value',
value: true,
previousValue: false,
component: null,
});
expect(emitStub).toHaveBeenCalledTimes(1);
expect(emitStub).toHaveBeenCalledWith(`update:${vue_helper_1.VMODEL_NAME}`, true);
expect(innerChanges[vue_helper_1.VMODEL_NAME]).toBe(true);
});
});