mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Remove outdated tests, fix imports
This commit is contained in:
@@ -66,7 +66,8 @@
|
||||
"dependencies": {
|
||||
"@directus/app": "file:../app",
|
||||
"@directus/format-title": "file:../packages/format-title",
|
||||
"@directus/specs": "file:../packages/spec",
|
||||
"@directus/specs": "file:../packages/specs",
|
||||
"@directus/schema": "file:../packages/schema",
|
||||
"@godaddy/terminus": "^4.4.1",
|
||||
"@slynova/flydrive": "^1.0.3",
|
||||
"@slynova/flydrive-gcs": "^1.0.3",
|
||||
@@ -103,7 +104,6 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"keyv": "^4.0.3",
|
||||
"knex": "^0.21.12",
|
||||
"knex-schema-inspector": "^0.0.25",
|
||||
"liquidjs": "^9.16.1",
|
||||
"lodash": "^4.17.20",
|
||||
"macos-release": "^2.4.1",
|
||||
|
||||
@@ -6,7 +6,7 @@ import logger from '../logger';
|
||||
import env from '../env';
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../', '.env') });
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AST, NestedCollectionNode, FieldNode, M2ONode, O2MNode } from '../types/ast';
|
||||
import { clone, cloneDeep, uniq, pick } from 'lodash';
|
||||
import database from './index';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { Query, Item } from '../types';
|
||||
import { PayloadService } from '../services/payload';
|
||||
import applyQuery from '../utils/apply-query';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Item,
|
||||
PrimaryKey,
|
||||
} from '../types';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import Knex from 'knex';
|
||||
import { ForbiddenException, FailedValidationException } from '../exceptions';
|
||||
import { uniq, merge, flatten } from 'lodash';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '../types';
|
||||
import Knex from 'knex';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { FieldsService } from '../services/fields';
|
||||
import { ItemsService } from '../services/items';
|
||||
import cache from '../cache';
|
||||
|
||||
@@ -10,7 +10,7 @@ import Knex, { CreateTableBuilder } from 'knex';
|
||||
import { PayloadService } from '../services/payload';
|
||||
import getDefaultValue from '../utils/get-default-value';
|
||||
import cache from '../cache';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
import { systemFieldRows } from '../database/system-data/fields/';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import database from '../database';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import runAST from '../database/run-ast';
|
||||
import getASTFromQuery from '../utils/get-ast-from-query';
|
||||
import {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ItemsService } from './items';
|
||||
import { URL } from 'url';
|
||||
import Knex from 'knex';
|
||||
import env from '../env';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import getLocalType from '../utils/get-local-type';
|
||||
import { format, formatISO } from 'date-fns';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AbstractServiceOptions, Accountability, PrimaryKey } from '../types';
|
||||
import database from '../database';
|
||||
import Knex from 'knex';
|
||||
import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class UtilsService {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Field } from './field';
|
||||
import { Table } from 'knex-schema-inspector/lib/types/table';
|
||||
import { Table } from '@directus/schema/dist/types/table';
|
||||
|
||||
export type CollectionMeta = {
|
||||
collection: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column } from 'knex-schema-inspector/lib/types/column';
|
||||
import { Column } from '@directus/schema/dist/types/column';
|
||||
|
||||
export const types = [
|
||||
'bigInteger',
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import database from '../database';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import Knex from 'knex';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { getRelationType } from '../utils/get-relation-type';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column } from 'knex-schema-inspector/dist/types/column';
|
||||
import { Column } from '@directus/schema/dist/types/column';
|
||||
import getLocalType from './get-local-type';
|
||||
|
||||
export default function getDefaultValue(column: Column) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column } from 'knex-schema-inspector/lib/types/column';
|
||||
import { Column } from '@directus/schema/dist/types/column';
|
||||
import { FieldMeta, types } from '../types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,16 +32,4 @@ module.exports = {
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.test.{js,ts}?(x)', '**/*.story.{js,ts}?(x)'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-function': 0,
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
export default function mountComposable(cb: () => any, mountOptions?: Parameters<typeof mount>[1]) {
|
||||
return mount(
|
||||
{
|
||||
setup() {
|
||||
return cb();
|
||||
},
|
||||
render(h) {
|
||||
return h('div');
|
||||
}
|
||||
},
|
||||
{
|
||||
localVue,
|
||||
...mountOptions
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -26,32 +26,26 @@ If you want to help out in the new version, please open an issue to discuss what
|
||||
|
||||
## Development
|
||||
|
||||
You need Node.js v10+ and Yarn.
|
||||
You need Node.js v10+
|
||||
|
||||
After cloning the repo, run:
|
||||
|
||||
```
|
||||
$ yarn # install all dependencies of the project
|
||||
$ npm install # install all dependencies of the project
|
||||
```
|
||||
|
||||
### Scripts
|
||||
|
||||
#### `yarn serve`
|
||||
#### `npm run serve`
|
||||
|
||||
Bundles the app in dev mode and watches for changes.
|
||||
|
||||
Combine this with the `API_URL` environment variable to point it to a running API instance to debug fully:
|
||||
|
||||
```
|
||||
$ API_URL=https://local.api.com yarn serve
|
||||
$ API_URL=https://local.api.com npm serve
|
||||
```
|
||||
|
||||
#### `yarn storybook`
|
||||
#### `npm run storybook`
|
||||
|
||||
Fires up an instance of Storybook and watches for changes. Very useful to develop individual components in isolation.
|
||||
|
||||
#### `yarn test`
|
||||
|
||||
Runs all Jest tests in the app. Add the `--coverage` flag to see a print of what test coverage you've achieved.
|
||||
|
||||
Please aim for 100% coverage for newly added code.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
const esModules = ['@popperjs/core'].join('|');
|
||||
|
||||
module.exports = {
|
||||
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
coveragePathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.jest/'],
|
||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||
setupFilesAfterEnv: ['<rootDir>/.jest/before-all.js'],
|
||||
restoreMocks: true,
|
||||
clearMocks: true,
|
||||
resetMocks: true,
|
||||
reporters: [
|
||||
'default',
|
||||
[
|
||||
'jest-sonar',
|
||||
{
|
||||
outputDirectory: 'coverage',
|
||||
outputName: 'sonar.xml',
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -81,7 +81,6 @@
|
||||
"@vue/cli-plugin-eslint": "^4.5.8",
|
||||
"@vue/cli-plugin-router": "^4.5.8",
|
||||
"@vue/cli-plugin-typescript": "^4.5.8",
|
||||
"@vue/cli-plugin-unit-jest": "^4.5.8",
|
||||
"@vue/cli-plugin-vuex": "^4.5.8",
|
||||
"@vue/cli-service": "^4.5.8",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI, { defineComponent, ref } from '@vue/composition-api';
|
||||
import TransitionExpand from './transition-expand.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
localVue.component('transition-expand', TransitionExpand);
|
||||
|
||||
const ExpandTestUtil = defineComponent({
|
||||
setup() {
|
||||
const active = ref<boolean>(false);
|
||||
const toggle = () => (active.value = !active.value);
|
||||
return { active, toggle };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<slot v-bind="{ active, toggle }" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
describe('Expand Transition', () => {
|
||||
it('Renders the provided markup in the default slot', async () => {
|
||||
const component = mount(ExpandTestUtil, {
|
||||
localVue,
|
||||
scopedSlots: {
|
||||
default: `
|
||||
<div slot-scope="foo">
|
||||
<button @click="foo.toggle"/>
|
||||
<transition-expand>
|
||||
<div v-show="foo.active" class="test"> Content </div>
|
||||
</transition-expand>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.test').isVisible()).toBe(false);
|
||||
component.find('button').trigger('click');
|
||||
await component.vm.$nextTick();
|
||||
expect(component.find('.test').isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VAvatar from './v-avatar.vue';
|
||||
|
||||
describe('Avatar', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => (component = mount(VAvatar, { localVue })));
|
||||
|
||||
it('Sets the tile class if tile prop is passed', async () => {
|
||||
component.setProps({ tile: true });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toContain('tile');
|
||||
});
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import VBadge from './v-badge.vue';
|
||||
import VIcon from '../v-icon/';
|
||||
|
||||
describe('Chip', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VBadge, { localVue });
|
||||
});
|
||||
|
||||
it('Adds the dot prop to the badge', async () => {
|
||||
component.setProps({
|
||||
dot: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('dot');
|
||||
});
|
||||
|
||||
it('Adds the bordered prop to the badge', async () => {
|
||||
component.setProps({
|
||||
bordered: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('bordered');
|
||||
});
|
||||
|
||||
it('Display the badge on the left', async () => {
|
||||
component.setProps({
|
||||
left: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('left');
|
||||
});
|
||||
|
||||
it('Display the badge on the bottom', async () => {
|
||||
component.setProps({
|
||||
bottom: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('bottom');
|
||||
});
|
||||
|
||||
it('Checks if the icon exists and if the name matches', async () => {
|
||||
component.setProps({
|
||||
icon: 'add',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.v-icon').exists()).toBe(true);
|
||||
expect(component.find('.v-icon').props().name).toBe('add');
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VBreadcrumb from './v-breadcrumb.vue';
|
||||
import VIcon from '../v-icon/';
|
||||
|
||||
import VueRouter from 'vue-router';
|
||||
|
||||
const router = new VueRouter();
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.use(VueRouter);
|
||||
|
||||
describe('Breadcrumb', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VBreadcrumb, { localVue, router });
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('Renders the whole breadcrump', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{ name: 'A', to: 'linkA' },
|
||||
{ name: 'B', to: 'linkB' },
|
||||
{ name: 'C', to: 'linkC' },
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
const sections = component.findAll('.section a.section-link');
|
||||
|
||||
expect(sections.at(0).text()).toBe('A');
|
||||
expect(sections.at(0).attributes().href).toContain('linkA');
|
||||
|
||||
expect(sections.at(1).text()).toBe('B');
|
||||
expect(sections.at(1).attributes().href).toContain('linkB');
|
||||
|
||||
expect(sections.at(2).text()).toBe('C');
|
||||
expect(sections.at(2).attributes().href).toContain('linkC');
|
||||
});
|
||||
|
||||
it('Renders breadcrumb with icon ', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{ name: 'A', to: 'linkA' },
|
||||
{ name: 'B', to: 'linkB', icon: 'home' },
|
||||
{ name: 'C', to: 'linkC', icon: 'add' },
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
const sections = component.findAll('.section a.section-link');
|
||||
|
||||
expect(sections.at(0).find('.v-icon').exists()).toBe(false);
|
||||
expect(sections.at(1).find('.v-icon').text()).toBe('home');
|
||||
expect(sections.at(2).find('.v-icon').text()).toBe('add');
|
||||
});
|
||||
|
||||
it('Renders breadcrumb with disabled section ', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{ name: 'A', to: 'linkA' },
|
||||
{ name: 'B', to: 'linkB', disabled: true },
|
||||
{ name: 'C', to: 'linkC' },
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.findAll('.section').at(1).classes()).toContain('disabled');
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VButtonGroup from './v-button-group.vue';
|
||||
import VItemGroup from '@/components/v-item-group/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item-group', VItemGroup);
|
||||
|
||||
jest.mock('@/composables/groupable');
|
||||
|
||||
describe('Components / Button Group', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VButtonGroup, { localVue });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import VCard from './v-card.vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Components / Card', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VCard, { localVue });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,133 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VIcon from '../v-icon/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import VCheckbox from './v-checkbox.vue';
|
||||
|
||||
describe('Checkbox', () => {
|
||||
it('Renders passed label', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
label: 'Turn me on',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('span[class="label type-text"]').text()).toContain('Turn me on');
|
||||
});
|
||||
|
||||
it('Renders as checked when inputValue `true` is given', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
inputValue: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('Calculates check for array inputValue', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['red'],
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('Emits true when state is false', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
inputValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toBe(true);
|
||||
});
|
||||
|
||||
it('Disables the button when disabled prop is set', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
expect(Object.keys(button.attributes())).toContain('disabled');
|
||||
});
|
||||
|
||||
it('Appends value to array', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['blue', 'green'],
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toEqual(['blue', 'green', 'red']);
|
||||
});
|
||||
|
||||
it('Removes value from array', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['blue', 'green', 'red'],
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toEqual(['blue', 'green']);
|
||||
});
|
||||
|
||||
it('Renders the correct icon for state', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
inputValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).icon).toBe('check_box_outline_blank');
|
||||
|
||||
component.setProps({ inputValue: true });
|
||||
|
||||
expect((component.vm as any).icon).toBe('check_box');
|
||||
|
||||
component.setProps({ indeterminate: true });
|
||||
|
||||
expect((component.vm as any).icon).toBe('indeterminate_check_box');
|
||||
});
|
||||
|
||||
it('Emits the update:indeterminate event when the checkbox is toggled when indeterminate', () => {
|
||||
const component = mount(VCheckbox, {
|
||||
localVue,
|
||||
propsData: {
|
||||
indeterminate: true,
|
||||
},
|
||||
});
|
||||
|
||||
component.find('button').trigger('click');
|
||||
|
||||
expect(component.emitted('update:indeterminate')?.[0]).toEqual([false]);
|
||||
});
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VChip from './v-chip.vue';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Chip', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VChip, { localVue });
|
||||
});
|
||||
|
||||
it('Renders the provided markup in the default slow', () => {
|
||||
const component = mount(VChip, {
|
||||
localVue,
|
||||
slots: {
|
||||
default: 'Click me',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.text()).toContain('Click me');
|
||||
});
|
||||
|
||||
it('Hides the whole component', async () => {
|
||||
component.setProps({
|
||||
active: false,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('span').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('Adds the outline class for outline chips', async () => {
|
||||
component.setProps({
|
||||
outlined: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.classes()).toContain('outlined');
|
||||
});
|
||||
|
||||
it('Adds the label class for block chips', async () => {
|
||||
component.setProps({
|
||||
label: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.classes()).toContain('label');
|
||||
});
|
||||
|
||||
it('Adds the close icon for icon chips', async () => {
|
||||
component.setProps({
|
||||
close: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.close-outline').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Emits a click event when chip is not disabled', async () => {
|
||||
component.setProps({
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).onClick(new Event('click'));
|
||||
|
||||
expect(component.emitted('click')?.[0][0]).toBeInstanceOf(Event);
|
||||
});
|
||||
|
||||
it('Does not emit click when disabled', async () => {
|
||||
component.setProps({
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).onClick(new Event('click'));
|
||||
|
||||
expect(component.emitted('click')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('Emits a click event when chip is not disabled and close button is clicked', async () => {
|
||||
component.setProps({
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).onCloseClick(new Event('click'));
|
||||
|
||||
expect(component.emitted('close')?.[0][0]).toBeInstanceOf(Event);
|
||||
});
|
||||
|
||||
it('Does not emit click when disabled and close button is clicked', async () => {
|
||||
component.setProps({
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).onCloseClick(new Event('click'));
|
||||
|
||||
expect(component.emitted('click')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import PortalVue from 'portal-vue';
|
||||
import VDialog from './v-dialog.vue';
|
||||
import VOverlay from '@/components/v-overlay';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-overlay', VOverlay);
|
||||
localVue.use(PortalVue);
|
||||
|
||||
describe('Components / Dialog', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VDialog, { localVue });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
|
||||
it('Calls emit on emitToggle when persistent is false', () => {
|
||||
const component = shallowMount(VDialog, {
|
||||
localVue,
|
||||
propsData: {
|
||||
persistent: false,
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
(component.vm as any).emitToggle();
|
||||
|
||||
expect(component.emitted('toggle')![0][0]).toBe(true);
|
||||
});
|
||||
|
||||
it('Sets and removes the nudge class when persistent is true', () => {
|
||||
const component = shallowMount(VDialog, {
|
||||
localVue,
|
||||
propsData: {
|
||||
persistent: true,
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
(component.vm as any).emitToggle();
|
||||
|
||||
expect(component.emitted('toggle')).toBe(undefined);
|
||||
expect((component.vm as any).className).toBe('nudge');
|
||||
});
|
||||
|
||||
it('Adds the nudge class', () => {
|
||||
const component = shallowMount(VDialog, {
|
||||
localVue,
|
||||
});
|
||||
|
||||
(component.vm as any).nudge();
|
||||
|
||||
expect((component.vm as any).className).toBe('nudge');
|
||||
|
||||
jest.advanceTimersByTime(250);
|
||||
|
||||
expect((component.vm as any).className).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VDivider from './v-divider.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Components / Divider', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VDivider, { localVue });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VHover from './v-hover.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Hover', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VHover, { localVue });
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('Renders the correct element based on tag prop', async () => {
|
||||
component.setProps({ tag: 'span' });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.find('span').exists()).toBe(true);
|
||||
component.setProps({ tag: 'section' });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.find('section').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Keeps track of the hover state', async () => {
|
||||
component.find('div').trigger('mouseenter');
|
||||
jest.runAllTimers();
|
||||
expect((component.vm as any).hover).toBe(true);
|
||||
|
||||
component.find('div').trigger('mouseleave');
|
||||
jest.runAllTimers();
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
});
|
||||
|
||||
it('Adds delays to enter/leave based on props', async () => {
|
||||
component.setProps({
|
||||
openDelay: 300,
|
||||
closeDelay: 600,
|
||||
});
|
||||
|
||||
component.find('div').trigger('mouseenter');
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
|
||||
jest.advanceTimersByTime(150);
|
||||
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
|
||||
expect((component.vm as any).hover).toBe(true);
|
||||
|
||||
component.find('div').trigger('mouseleave');
|
||||
|
||||
jest.advanceTimersByTime(300);
|
||||
|
||||
expect((component.vm as any).hover).toBe(true);
|
||||
|
||||
jest.advanceTimersByTime(300);
|
||||
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
});
|
||||
|
||||
it("Doesn't do anything if disabled prop is set", async () => {
|
||||
component.setProps({ disabled: true });
|
||||
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
|
||||
component.find('div').trigger('mouseenter');
|
||||
jest.runAllTimers();
|
||||
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
|
||||
component.find('div').trigger('mouseleave');
|
||||
jest.runAllTimers();
|
||||
|
||||
expect((component.vm as any).hover).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VIcon from './v-icon.vue';
|
||||
|
||||
describe('Icon', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VIcon, { localVue, propsData: { name: 'person' } });
|
||||
});
|
||||
|
||||
it('Renders custom icons as inline <svg>', async () => {
|
||||
component.setProps({
|
||||
name: 'box',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.contains('svg')).toBe(true);
|
||||
});
|
||||
|
||||
it('Supports superscript size class', async () => {
|
||||
component.setProps({
|
||||
sup: true,
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: false,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toContain('sup');
|
||||
});
|
||||
|
||||
it('Adds the has-click class if a click event is passed', async () => {
|
||||
const component = mount(VIcon, {
|
||||
localVue,
|
||||
propsData: {
|
||||
name: 'person',
|
||||
},
|
||||
listeners: {
|
||||
click: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.classes()).toContain('has-click');
|
||||
});
|
||||
|
||||
it('Sets the left / right classes if props are given', async () => {
|
||||
component.setProps({
|
||||
left: true,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toContain('left');
|
||||
|
||||
component.setProps({
|
||||
left: false,
|
||||
right: true,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toContain('right');
|
||||
});
|
||||
|
||||
it('Emits the click event on click of the icon', () => {
|
||||
component.find('span').trigger('click');
|
||||
expect(component.emitted('click')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import VInfo from './v-info.vue';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Components / Info', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VInfo, { localVue, propsData: { title: 'test' } });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.directive('focus', {});
|
||||
|
||||
import VInput from './v-input.vue';
|
||||
|
||||
describe('Input', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VInput, { localVue });
|
||||
});
|
||||
|
||||
it('Renders content in the correct slots', async () => {
|
||||
component = mount(VInput, {
|
||||
localVue,
|
||||
slots: {
|
||||
'prepend-outer': '<div>prepend-outer</div>',
|
||||
prepend: '<div>prepend</div>',
|
||||
append: '<div>append</div>',
|
||||
'append-outer': '<div>append-outer</div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.v-input > .prepend-outer > div ').html()).toBe('<div>prepend-outer</div>');
|
||||
expect(component.find('.v-input > .append-outer > div ').html()).toBe('<div>append-outer</div>');
|
||||
expect(component.find('.v-input > .input > .prepend > div ').html()).toBe('<div>prepend</div>');
|
||||
expect(component.find('.v-input > .input > .append > div ').html()).toBe('<div>append</div>');
|
||||
});
|
||||
|
||||
it('Renders prefix / suffix', async () => {
|
||||
component.setProps({
|
||||
prefix: 'Prefix',
|
||||
suffix: 'Suffix',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.input .prefix').html()).toBe('<span class="prefix">Prefix</span>');
|
||||
expect(component.find('.input .suffix').html()).toBe('<span class="suffix">Suffix</span>');
|
||||
});
|
||||
|
||||
it('Sets the correct classes based on props', async () => {
|
||||
component.setProps({
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.input').classes()).toEqual(['input', 'disabled']);
|
||||
});
|
||||
|
||||
it('Emits just the value for the input event', async () => {
|
||||
const input = component.find('input');
|
||||
(input.element as HTMLInputElement).value = 'The value';
|
||||
input.trigger('input');
|
||||
expect(component.emitted('input')?.[0]).toEqual(['The value']);
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VItemGroup from './v-item-group.vue';
|
||||
import * as composition from '@/composables/groupable/groupable';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item-group', VItemGroup);
|
||||
|
||||
describe('Components / Item Group', () => {
|
||||
it('Calls the useGroupableParent composition', () => {
|
||||
jest.spyOn(composition, 'useGroupableParent');
|
||||
mount(VItemGroup, { localVue });
|
||||
expect(composition.useGroupableParent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Emits the input event on selection changes in the groupable composition', () => {
|
||||
jest.spyOn(composition, 'useGroupableParent').mockImplementation((state: any): any => {
|
||||
state.onSelectionChange([1, 2, 3]);
|
||||
});
|
||||
const component = mount(VItemGroup, { localVue });
|
||||
expect(component.emitted('input')?.[0][0]).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VItem from './v-item.vue';
|
||||
import * as composable from '@/composables/groupable/groupable';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item', VItem);
|
||||
|
||||
describe('Components / Item Group / Item', () => {
|
||||
it('Calls the groupable composable', () => {
|
||||
jest.spyOn(composable, 'useGroupable');
|
||||
mount(VItem, {
|
||||
localVue,
|
||||
provide: {
|
||||
'item-group': {
|
||||
register: jest.fn(),
|
||||
unregister: jest.fn(),
|
||||
toggle: jest.fn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(composable.useGroupable).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI, { defineComponent } from '@vue/composition-api';
|
||||
import ClickOutside from '@/directives/click-outside/';
|
||||
import VMenu from './v-menu.vue';
|
||||
import VButton from '@/components/v-button/';
|
||||
import VList, { VListItem } from '@/components/v-list/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.directive('click-outside', ClickOutside);
|
||||
|
||||
describe('Components / Menu', () => {
|
||||
it('Renders', () => {
|
||||
const testComponent = defineComponent({
|
||||
components: { VMenu, VButton, VList, VListItem },
|
||||
template: `
|
||||
<v-menu>
|
||||
<template #activator="{ toggle }">
|
||||
<v-button @click="toggle">Click me</v-button>
|
||||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list-item>Test</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
`,
|
||||
});
|
||||
|
||||
const component = shallowMount(testComponent, { localVue });
|
||||
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VOverlay from './v-overlay.vue';
|
||||
|
||||
describe('Overlay', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = shallowMount(VOverlay, {
|
||||
localVue,
|
||||
});
|
||||
});
|
||||
|
||||
it('Is invisible when active prop is false', () => {
|
||||
expect(component.classes()).toEqual(['v-overlay']);
|
||||
});
|
||||
|
||||
it('Is visible when active is true', async () => {
|
||||
component.setProps({ active: true });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toEqual(['v-overlay', 'active']);
|
||||
});
|
||||
|
||||
it('Sets position absolute based on absolute prop', async () => {
|
||||
component.setProps({ active: true, absolute: true });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.classes()).toContain('absolute');
|
||||
});
|
||||
|
||||
it('Adds the has-click class when click event is passed', async () => {
|
||||
const component = shallowMount(VOverlay, {
|
||||
localVue,
|
||||
listeners: {
|
||||
click: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.classes()).toContain('has-click');
|
||||
});
|
||||
|
||||
it('Emits click event', async () => {
|
||||
component.find('.v-overlay').trigger('click');
|
||||
expect(component.emitted('click')?.[0]).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VPagination from './v-pagination.vue';
|
||||
|
||||
import VButton from '@/components/v-button';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Components / Pagination', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = shallowMount(VPagination, {
|
||||
localVue,
|
||||
propsData: {
|
||||
length: 5,
|
||||
totalVisible: 5,
|
||||
value: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('Visible Pages', () => {
|
||||
it('Renders all pages when totalVisible is not set', async () => {
|
||||
component.setProps({
|
||||
totalVisible: undefined,
|
||||
length: 5,
|
||||
value: 1,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).visiblePages).toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('Starts at 1 when current value is less than half of totalVisible', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 5,
|
||||
length: 15,
|
||||
value: 2,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).visiblePages).toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('Renders the current value in the middle', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 5,
|
||||
length: 15,
|
||||
value: 5,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).visiblePages).toEqual([3, 4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
it('Renders the current value on the right of the the middle when totalVisible is even', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 6,
|
||||
length: 15,
|
||||
value: 7,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).visiblePages).toEqual([4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it('Renders the last numbers when value is in last half of length', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 6,
|
||||
length: 15,
|
||||
value: 14,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).visiblePages).toEqual([10, 11, 12, 13, 14, 15]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls emit with value - 1 on toPrev', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 5,
|
||||
length: 10,
|
||||
value: 3,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).toPrev();
|
||||
|
||||
expect(component.emitted('input')?.[0][0]).toBe(2);
|
||||
});
|
||||
|
||||
it('Calls emit with value + 1 on toNext', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 5,
|
||||
length: 10,
|
||||
value: 3,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).toNext();
|
||||
|
||||
expect(component.emitted('input')?.[0][0]).toBe(4);
|
||||
});
|
||||
|
||||
it('Calls emit with value on toPage', async () => {
|
||||
component.setProps({
|
||||
totalVisible: 5,
|
||||
length: 10,
|
||||
value: 3,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).toPage(9);
|
||||
|
||||
expect(component.emitted('input')?.[0][0]).toBe(9);
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VProgressCircular from './v-progress-circular.vue';
|
||||
|
||||
describe('Spinner', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => (component = mount(VProgressCircular, { localVue })));
|
||||
|
||||
it('Adds the correct classes based on props', async () => {
|
||||
component.setProps({
|
||||
indeterminate: true,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect(component.find('svg').classes()).toContain('indeterminate');
|
||||
});
|
||||
|
||||
it('Calculates the correct stroke-dasharray', async () => {
|
||||
component.setProps({
|
||||
value: 0,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).circleStyle).toEqual({
|
||||
'stroke-dasharray': '0, 78.5',
|
||||
});
|
||||
|
||||
component.setProps({
|
||||
value: 25,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).circleStyle).toEqual({
|
||||
'stroke-dasharray': '19.625, 78.5',
|
||||
});
|
||||
|
||||
component.setProps({
|
||||
value: 50,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).circleStyle).toEqual({
|
||||
'stroke-dasharray': '39.25, 78.5',
|
||||
});
|
||||
|
||||
component.setProps({
|
||||
value: 75,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).circleStyle).toEqual({
|
||||
'stroke-dasharray': '58.875, 78.5',
|
||||
});
|
||||
|
||||
component.setProps({
|
||||
value: 100,
|
||||
});
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).circleStyle).toEqual({
|
||||
'stroke-dasharray': '78.5, 78.5',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VProgressLinear from './v-progress-linear.vue';
|
||||
|
||||
describe('Progress (Linear)', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VProgressLinear, { localVue });
|
||||
});
|
||||
|
||||
it('Sets the correct classes based on the props', async () => {
|
||||
component.setProps({
|
||||
absolute: true,
|
||||
bottom: false,
|
||||
fixed: false,
|
||||
indeterminate: false,
|
||||
rounded: false,
|
||||
top: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.classes()).toEqual(['v-progress-linear', 'absolute', 'top']);
|
||||
|
||||
component.setProps({
|
||||
absolute: false,
|
||||
bottom: true,
|
||||
fixed: true,
|
||||
indeterminate: false,
|
||||
rounded: false,
|
||||
top: false,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.classes()).toEqual(['v-progress-linear', 'bottom', 'fixed']);
|
||||
|
||||
component.setProps({
|
||||
absolute: false,
|
||||
bottom: false,
|
||||
fixed: false,
|
||||
indeterminate: true,
|
||||
rounded: true,
|
||||
top: false,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.classes()).toEqual(['v-progress-linear', 'indeterminate', 'rounded']);
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VRadio from './v-radio.vue';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Components / Radio', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VRadio, { localVue, propsData: { value: 'red' } });
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
|
||||
it('Calculates the checked state correctly', async () => {
|
||||
const component = shallowMount(VRadio, { localVue, propsData: { value: 'red' } });
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(false);
|
||||
|
||||
component.setProps({
|
||||
value: 'red',
|
||||
inputValue: 'red',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(true);
|
||||
|
||||
component.setProps({
|
||||
value: 'red',
|
||||
inputValue: 'blue',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(false);
|
||||
});
|
||||
|
||||
it('Uses the right material icon when checked', async () => {
|
||||
const component = shallowMount(VRadio, { localVue, propsData: { value: 'red' } });
|
||||
|
||||
expect((component.vm as any).icon).toBe('radio_button_unchecked');
|
||||
|
||||
component.setProps({
|
||||
value: 'red',
|
||||
inputValue: 'red',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).icon).toBe('radio_button_checked');
|
||||
});
|
||||
|
||||
it('Emits the value on click', () => {
|
||||
const component = shallowMount(VRadio, { localVue, propsData: { value: 'red' } });
|
||||
|
||||
component.find('button').trigger('click');
|
||||
|
||||
expect(component.emitted('change')?.[0][0]).toBe('red');
|
||||
});
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
import VSelect from './v-select.vue';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VMenu from '@/components/v-menu/';
|
||||
import VList, { VListItem, VListItemContent, VListItemText } from '@/components/v-list/';
|
||||
import VCheckbox from '@/components/v-checkbox/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-menu', VMenu);
|
||||
localVue.component('v-list', VList);
|
||||
localVue.component('v-list-item', VListItem);
|
||||
localVue.component('v-list-item-content', VListItemContent);
|
||||
localVue.component('v-list-item-text', VListItemText);
|
||||
localVue.component('v-checkbox', VCheckbox);
|
||||
|
||||
describe('Components / Select', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VSelect, {
|
||||
localVue,
|
||||
propsData: {
|
||||
items: [
|
||||
{ text: 'test', value: 1 },
|
||||
{ text: 'test', value: 2 },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
|
||||
it('Converts the items to a standardized array', async () => {
|
||||
const component = shallowMount(VSelect, {
|
||||
localVue,
|
||||
propsData: {
|
||||
items: ['Item 1', 'Item 2'],
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
text: 'Item 1',
|
||||
value: 'Item 1',
|
||||
},
|
||||
{
|
||||
text: 'Item 2',
|
||||
value: 'Item 2',
|
||||
},
|
||||
]);
|
||||
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
test: 'item 1',
|
||||
another: 'value',
|
||||
},
|
||||
],
|
||||
itemText: 'test',
|
||||
itemValue: 'another',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
text: 'item 1',
|
||||
value: 'value',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Calculates the displayValue based on the given value', async () => {
|
||||
const component = shallowMount(VSelect, {
|
||||
localVue,
|
||||
propsData: {
|
||||
items: ['Item 1', 'Item 2'],
|
||||
value: 'Item 1',
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).displayValue).toBe('Item 1');
|
||||
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
text: 'Item 1',
|
||||
value: 'item-1',
|
||||
},
|
||||
],
|
||||
value: 'item-1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).displayValue).toBe('Item 1');
|
||||
|
||||
component.setProps({
|
||||
itemText: 'test',
|
||||
itemValue: 'test2',
|
||||
items: [
|
||||
{
|
||||
test: 'Item 1',
|
||||
test2: 'item-1',
|
||||
},
|
||||
],
|
||||
value: 'item-1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).displayValue).toBe('Item 1');
|
||||
|
||||
component.setProps({
|
||||
itemText: 'text',
|
||||
itemValue: 'value',
|
||||
items: [
|
||||
{
|
||||
text: 'Item 1',
|
||||
value: 'item-1',
|
||||
},
|
||||
{
|
||||
text: 'Item 2',
|
||||
value: 'item-2',
|
||||
},
|
||||
],
|
||||
multiple: true,
|
||||
value: ['item-1', 'item-2'],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).displayValue).toBe('Item 1, Item 2');
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VSheet from './v-sheet.vue';
|
||||
|
||||
describe('Sheet', () => {
|
||||
it('Renders the passed slot', async () => {
|
||||
const component = mount(VSheet, {
|
||||
localVue,
|
||||
slots: {
|
||||
default: '<div>Hello</div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.v-sheet > *').html()).toBe('<div>Hello</div>');
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { createLocalVue, mount, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VSlider from './v-slider.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Slider', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VSlider, { localVue });
|
||||
});
|
||||
|
||||
it('Calculates the correct percentage based on props/value', async () => {
|
||||
component.setProps({
|
||||
min: 5,
|
||||
max: 25,
|
||||
value: 10,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).styles['--_v-slider-percentage']).toEqual(25);
|
||||
});
|
||||
|
||||
it('Emits just the value on input', async () => {
|
||||
const input = component.find('input');
|
||||
(input.element as HTMLInputElement).value = '500';
|
||||
input.trigger('input');
|
||||
|
||||
expect(component.emitted('input')?.[0]).toEqual([500]);
|
||||
});
|
||||
|
||||
it('Emits just the value on change', async () => {
|
||||
const input = component.find('input');
|
||||
(input.element as HTMLInputElement).value = '500';
|
||||
input.trigger('change');
|
||||
|
||||
expect(component.emitted('change')?.[0]).toEqual([500]);
|
||||
});
|
||||
|
||||
it('Renders the prepend/append slots', async () => {
|
||||
const component = mount(VSlider, {
|
||||
localVue,
|
||||
slots: {
|
||||
prepend: '<div>prepend</div>',
|
||||
append: '<div>append</div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.prepend > div').html()).toBe('<div>prepend</div>');
|
||||
expect(component.find('.append > div').html()).toBe('<div>append</div>');
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VSwitch from './v-switch.vue';
|
||||
|
||||
describe('Switch', () => {
|
||||
it('Renders passed label', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
label: 'Turn me on',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('span[class="label type-label"]').text()).toContain('Turn me on');
|
||||
});
|
||||
|
||||
it('Renders as checked when inputValue `true` is given', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
inputValue: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('Calculates check for array inputValue', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['red'],
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).isChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('Emits true when state is false', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
inputValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toBe(true);
|
||||
});
|
||||
|
||||
it('Disables the button when disabled prop is set', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
expect(Object.keys(button.attributes())).toContain('disabled');
|
||||
});
|
||||
|
||||
it('Appends value to array', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['blue', 'green'],
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toEqual(['blue', 'green', 'red']);
|
||||
});
|
||||
|
||||
it('Removes value from array', () => {
|
||||
const component = mount(VSwitch, {
|
||||
localVue,
|
||||
propsData: {
|
||||
value: 'red',
|
||||
inputValue: ['blue', 'green', 'red'],
|
||||
},
|
||||
});
|
||||
|
||||
const button = component.find('button');
|
||||
button.trigger('click');
|
||||
|
||||
expect(component.emitted()?.change?.[0][0]).toEqual(['blue', 'green']);
|
||||
});
|
||||
});
|
||||
@@ -1,295 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VCheckbox from '@/components/v-checkbox';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
localVue.component('v-checkbox', VCheckbox);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import TableHeader from './table-header.vue';
|
||||
|
||||
describe('Table / Header', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(TableHeader, {
|
||||
localVue,
|
||||
propsData: {
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: null,
|
||||
desc: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets the right classes for the passed props', async () => {
|
||||
component.setProps({
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
const classes = (component.vm as any).getClassesForHeader({
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
expect(classes).toEqual(['align-center', 'sortable', 'sort-desc']);
|
||||
});
|
||||
|
||||
it('Emits the correct update event on sorting changes', async () => {
|
||||
component.setProps({
|
||||
sort: {
|
||||
by: null,
|
||||
desc: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
expect(component.emitted('update:sort')?.[0]).toEqual([{ by: 'col1', desc: false }]);
|
||||
|
||||
component.setProps({
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
|
||||
expect(component.emitted('update:sort')?.[1]).toEqual([{ by: 'col1', desc: true }]);
|
||||
|
||||
component.setProps({
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
expect(component.emitted('update:sort')?.[2]).toEqual([{ by: null, desc: false }]);
|
||||
});
|
||||
|
||||
it("Doesn't emit the update sort event when dragging", async () => {
|
||||
(component.vm as any).dragging = true;
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
expect(component.emitted('update:sort')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('Emits toggle-select-all on checkbox click', async () => {
|
||||
component.setProps({
|
||||
showSelect: true,
|
||||
sort: {
|
||||
by: null,
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find(VCheckbox).trigger('click');
|
||||
|
||||
expect(component.emitted('toggle-select-all')?.[0]).toEqual([true]);
|
||||
});
|
||||
|
||||
it('Prevents unsortable columns from being sorted', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
|
||||
expect(component.emitted()).toEqual({});
|
||||
});
|
||||
|
||||
it('Renders correct thead for provided headers', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: 'Col2',
|
||||
value: 'col2',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('th:first-child').html()).toContain('Col1');
|
||||
expect(component.find('th:nth-child(2)').html()).toContain('Col2');
|
||||
});
|
||||
|
||||
it('Adds the align class to the header', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
text: 'Col2',
|
||||
value: 'col2',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
text: 'Col3',
|
||||
value: 'col3',
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('th:first-child').classes()).toContain('align-left');
|
||||
expect(component.find('th:nth-child(2)').classes()).toContain('align-center');
|
||||
expect(component.find('th:nth-child(3)').classes()).toContain('align-right');
|
||||
});
|
||||
|
||||
it('Renders the provided element in the nested scoped slot for the header', async () => {
|
||||
const component = mount(TableHeader, {
|
||||
localVue,
|
||||
propsData: {
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: null,
|
||||
desc: false,
|
||||
},
|
||||
},
|
||||
scopedSlots: {
|
||||
'header.col2': '<template slot-scope="{header}"><p>{{ header.text }}</p></template>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.table-header th:nth-child(2) .content > span > *').html()).toEqual('<p>Column 2</p>');
|
||||
});
|
||||
|
||||
it('Sets the dragging state correctly based on mouse interaction', async () => {
|
||||
component.setProps({
|
||||
showResize: true,
|
||||
headers: [
|
||||
{
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: 'Col2',
|
||||
value: 'col2',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).dragging).toBe(false);
|
||||
|
||||
component.find('.resize-handle').trigger('mousedown');
|
||||
|
||||
expect((component.vm as any).dragging).toBe(true);
|
||||
|
||||
window.dispatchEvent(new Event('mouseup'));
|
||||
|
||||
expect((component.vm as any).dragging).toBe(false);
|
||||
});
|
||||
|
||||
it('Calculates the new header size correctly', async () => {
|
||||
component.setProps({
|
||||
showResize: true,
|
||||
headers: [
|
||||
{
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: 'Col2',
|
||||
value: 'col2',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
// Set internal dragging state to dummy values after starting resize
|
||||
(component.vm as any).dragging = true;
|
||||
(component.vm as any).dragStartX = 0;
|
||||
(component.vm as any).dragStartWidth = 100;
|
||||
(component.vm as any).dragHeader = {
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
};
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any).onMouseMove({
|
||||
pageX: 50,
|
||||
});
|
||||
|
||||
expect(component.emitted('update:headers')?.[0][0][0].width).toBe(150);
|
||||
});
|
||||
|
||||
it("Doesn't trigger on mousemove if dragging is false", async () => {
|
||||
(component.vm as any).onMouseMove({
|
||||
pageX: 50,
|
||||
});
|
||||
|
||||
expect(component.emitted('update:headers')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VCheckbox from '@/components/v-checkbox/';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
|
||||
localVue.component('v-checkbox', VCheckbox);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import TableRow from './table-row.vue';
|
||||
|
||||
describe('Table / Row', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(TableRow, {
|
||||
localVue,
|
||||
propsData: {
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
item: {
|
||||
col1: 'Test',
|
||||
col2: 'Test 2',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Renders right amount of cells per row', async () => {
|
||||
expect(component.find('.table-row').findAll('td').length).toBe(3);
|
||||
});
|
||||
|
||||
it('Renders the provided element in the nested scoped slot', async () => {
|
||||
const component = mount(TableRow, {
|
||||
localVue,
|
||||
propsData: {
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
item: {
|
||||
col1: 'Test 1 Col 1',
|
||||
col2: 'Test 1 Col 2',
|
||||
},
|
||||
},
|
||||
scopedSlots: {
|
||||
'item.col2': '<template slot-scope="{item}"><p>{{ item.col2 }}</p></template>',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.table-row td:nth-child(2) > *').html()).toEqual('<p>Test 1 Col 2</p>');
|
||||
});
|
||||
|
||||
it('Adds the align class', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Col1',
|
||||
value: 'col1',
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
text: 'Col2',
|
||||
value: 'col2',
|
||||
sortable: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
text: 'Col3',
|
||||
value: 'col3',
|
||||
sortable: true,
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('td:first-child').classes()).toContain('align-left');
|
||||
expect(component.find('td:nth-child(2)').classes()).toContain('align-center');
|
||||
expect(component.find('td:nth-child(3)').classes()).toContain('align-right');
|
||||
});
|
||||
|
||||
it('Emits item selection changes on checkbox click', async () => {
|
||||
component.setProps({
|
||||
showSelect: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find(VCheckbox).trigger('click');
|
||||
|
||||
expect(component.emitted('item-selected')?.[0]).toEqual([
|
||||
{
|
||||
item: {
|
||||
col1: 'Test',
|
||||
col2: 'Test 2',
|
||||
},
|
||||
value: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,729 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
import VCheckbox from '@/components/v-checkbox/';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
|
||||
localVue.component('v-checkbox', VCheckbox);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import VTable from './v-table.vue';
|
||||
|
||||
describe('Table', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VTable, { localVue, propsData: { headers: [], items: [] } });
|
||||
});
|
||||
|
||||
it('Renders the correct amount of rows for the given items', async () => {
|
||||
component.setProps({ items: [{}, {}, {}] });
|
||||
await component.vm.$nextTick();
|
||||
expect(component.findAll('.table-row').length).toBe(3);
|
||||
});
|
||||
|
||||
it('Adds the defaults to the passed headers', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._headers).toEqual([
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
width: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Sorts the items based on the passed props', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Calculates if all items are selected', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).allItemsSelected).toEqual(true);
|
||||
expect((component.vm as any).someItemsSelected).toEqual(false);
|
||||
});
|
||||
|
||||
it('Calculates if some items are selected', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any).allItemsSelected).toEqual(false);
|
||||
expect((component.vm as any).someItemsSelected).toEqual(true);
|
||||
});
|
||||
|
||||
it('Handles sort by updates coming from header', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
|
||||
expect((component.vm as any)._sort.by).toEqual('col1');
|
||||
expect(component.emitted('update:sort')?.[0]).toEqual([{ by: 'col1', desc: false }]);
|
||||
});
|
||||
|
||||
it('Handles sort desc updates coming from header', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('th .content').trigger('click');
|
||||
|
||||
expect(component.emitted('update:sort')?.[0]).toEqual([{ by: 'col1', desc: true }]);
|
||||
});
|
||||
|
||||
it('Updates selection correctly', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [],
|
||||
showSelect: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('.table-row .select > *').trigger('click');
|
||||
|
||||
expect(component.emitted('select')?.[0]).toEqual([
|
||||
[
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
],
|
||||
showSelect: true,
|
||||
itemKey: 'col1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('.table-row .select > *').trigger('click');
|
||||
|
||||
expect(component.emitted('select')?.[1]).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('Calculates selected state per row', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
],
|
||||
showSelect: true,
|
||||
itemKey: 'col1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(
|
||||
(component.vm as any).getSelectedState({
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
})
|
||||
).toEqual(true);
|
||||
|
||||
expect(
|
||||
(component.vm as any).getSelectedState({
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('Selects all items if header checkbox is clicked', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
],
|
||||
showSelect: true,
|
||||
itemKey: 'col1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('.table-header .select > *').trigger('click');
|
||||
|
||||
expect(component.emitted('select')?.[0]).toEqual([
|
||||
[
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
selection: [
|
||||
{
|
||||
col1: 'A',
|
||||
col2: 3,
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
col2: 1,
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
col2: 2,
|
||||
},
|
||||
],
|
||||
showSelect: true,
|
||||
itemKey: 'col1',
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
component.find('.table-header .select > *').trigger('click');
|
||||
|
||||
expect(component.emitted('select')?.[1]).toEqual([[]]);
|
||||
});
|
||||
|
||||
describe('Sorting', () => {
|
||||
it('Sorts the items by the given sort prop internally', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Sorts the items in descending order if sort.desc is set to true', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Does not sort the items if the server-sort prop is set', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: 'col1',
|
||||
desc: false,
|
||||
},
|
||||
serverSort: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Does not sort if manual sorting is activated', async () => {
|
||||
component.setProps({
|
||||
items: [
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: '$manual',
|
||||
desc: false,
|
||||
},
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect((component.vm as any)._items).toEqual([
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Emits the update:items event when the internal items array is updated', async () => {
|
||||
component.setProps({
|
||||
items: [],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any)._items = [
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
];
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.emitted('update:items')?.[0]).toEqual([
|
||||
[
|
||||
{
|
||||
col1: 'A',
|
||||
},
|
||||
{
|
||||
col1: 'C',
|
||||
},
|
||||
{
|
||||
col1: 'B',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Emits updated headers without default values', async () => {
|
||||
component.setProps({
|
||||
headers: [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
(component.vm as any)._headers = [
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
width: null, // default, should be removed
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
width: 150, // should be staged
|
||||
},
|
||||
];
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.emitted('update:headers')?.[0]).toEqual([
|
||||
[
|
||||
{
|
||||
text: 'Column 1',
|
||||
value: 'col1',
|
||||
},
|
||||
{
|
||||
text: 'Column 2',
|
||||
value: 'col2',
|
||||
width: 150,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VTabItem from './v-tab-item.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
jest.mock('@/composables/groupable', () => ({
|
||||
useGroupable: () => ({
|
||||
active: { value: null },
|
||||
toggle: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Components / Tabs / Tab', () => {
|
||||
it('Renders when active', () => {
|
||||
const component = shallowMount(VTabItem, {
|
||||
localVue,
|
||||
data: () => ({
|
||||
active: true,
|
||||
}),
|
||||
});
|
||||
expect(component.find('.v-tab-item').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Does not render when inactive', () => {
|
||||
const component = shallowMount(VTabItem, {
|
||||
localVue,
|
||||
data: () => ({
|
||||
active: false,
|
||||
}),
|
||||
});
|
||||
expect(component.find('.v-tab-item').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VTab from './v-tab.vue';
|
||||
|
||||
const mockUseGroupableContent = {
|
||||
active: {
|
||||
value: false,
|
||||
},
|
||||
toggle: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('@/composables/groupable', () => ({
|
||||
useGroupable: () => mockUseGroupableContent,
|
||||
}));
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-tab', VTab);
|
||||
|
||||
describe('Components / Tabs / Tab', () => {
|
||||
it('Calls toggle on click', () => {
|
||||
const component = shallowMount(VTab, { localVue });
|
||||
component.trigger('click');
|
||||
expect(mockUseGroupableContent.toggle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Does not call toggle when disabled', () => {
|
||||
const component = shallowMount(VTab, {
|
||||
localVue,
|
||||
propsData: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
component.trigger('click');
|
||||
expect(mockUseGroupableContent.toggle).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VTabsItems from './v-tabs-items.vue';
|
||||
|
||||
import VItemGroup from '@/components/v-item-group';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item-group', VItemGroup);
|
||||
|
||||
describe('Components / Tabs / Tabs Items', () => {
|
||||
it('Emits the new selection on update', () => {
|
||||
const component = shallowMount(VTabsItems, { localVue });
|
||||
(component.vm as any).update([1]);
|
||||
expect(component.emitted('input')?.[0][0]).toEqual([1]);
|
||||
});
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VTabs from './v-tabs.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item-group', VTabs);
|
||||
|
||||
jest.mock('@/composables/groupable', () => ({
|
||||
useGroupableParent: () => {
|
||||
return {
|
||||
items: {
|
||||
value: [
|
||||
{
|
||||
active: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
active: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
active: {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
active: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any;
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Components / Tabs', () => {
|
||||
it('Emits the input event on update', () => {
|
||||
const component = shallowMount(VTabs, { localVue });
|
||||
(component.vm as any).update(['a']);
|
||||
expect(component.emitted('input')?.[0][0]).toEqual(['a']);
|
||||
});
|
||||
|
||||
it('Calculates the correct css variables based on children groupable items', () => {
|
||||
const component = shallowMount(VTabs, { localVue });
|
||||
|
||||
expect((component.vm as any).slideStyle).toEqual({
|
||||
'--_v-tabs-items': 4,
|
||||
'--_v-tabs-selected': 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.directive('focus', {});
|
||||
|
||||
import VTextarea from './v-textarea.vue';
|
||||
|
||||
describe('Textarea', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VTextarea, { localVue });
|
||||
});
|
||||
|
||||
it('Sets the correct classes based on props', async () => {
|
||||
component.setProps({
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.v-textarea').classes()).toEqual(['v-textarea', 'disabled', 'full-width']);
|
||||
});
|
||||
|
||||
it('Emits just the value for the input event', async () => {
|
||||
const input = component.find('textarea');
|
||||
(input.element as HTMLInputElement).value = 'The value';
|
||||
input.trigger('input');
|
||||
expect(component.emitted('input')?.[0]).toEqual(['The value']);
|
||||
});
|
||||
});
|
||||
@@ -1,491 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import { provide, inject, ref } from '@vue/composition-api';
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
import { useGroupable, useGroupableParent } from './groupable';
|
||||
|
||||
describe('Groupable', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Child', () => {
|
||||
it('Returns on-ops if parent injection does not exist or is undefined', () => {
|
||||
mountComposable(() => {
|
||||
provide('item-group', undefined);
|
||||
const { active, toggle } = useGroupable();
|
||||
expect(active).toEqual({ value: false });
|
||||
expect(toggle).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls register on creation, and unregister on destroy', () => {
|
||||
const register = jest.fn();
|
||||
const unregister = jest.fn();
|
||||
const toggle = jest.fn();
|
||||
|
||||
const component = mountComposable(() => {
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
useGroupable();
|
||||
});
|
||||
|
||||
expect(register).toHaveBeenCalled();
|
||||
component.destroy();
|
||||
expect(unregister).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Passes the custom value to the parent scope on register', () => {
|
||||
const register = jest.fn();
|
||||
const unregister = jest.fn();
|
||||
const toggle = jest.fn();
|
||||
|
||||
mountComposable(() => {
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
useGroupable({ value: 'custom-value' });
|
||||
});
|
||||
|
||||
expect(register).toHaveBeenCalledWith({
|
||||
active: { value: false },
|
||||
value: 'custom-value',
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns the active state and a toggle function on succesful registration', () => {
|
||||
const register = jest.fn();
|
||||
const unregister = jest.fn();
|
||||
const toggle = jest.fn();
|
||||
|
||||
mountComposable(() => {
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
const result = useGroupable({ value: 'custom-value' });
|
||||
expect(result!.active).toEqual({ value: false });
|
||||
expect(result!.toggle).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls parent provided toggle on toggle', () => {
|
||||
const register = jest.fn();
|
||||
const unregister = jest.fn();
|
||||
const toggle = jest.fn();
|
||||
|
||||
mountComposable(() => {
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
const result = useGroupable({ value: 'custom-value' });
|
||||
result!.toggle();
|
||||
expect(toggle).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('Sets internal active state on toggle', () => {
|
||||
const register = jest.fn();
|
||||
const unregister = jest.fn();
|
||||
const toggle = jest.fn();
|
||||
|
||||
mountComposable(() => {
|
||||
provide('item-group', { register, unregister, toggle });
|
||||
const result = useGroupable({ value: 'custom-value' });
|
||||
result!.toggle();
|
||||
expect(result!.active).toEqual({ value: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parent', () => {
|
||||
describe('Registration', () => {
|
||||
it('Provides register, unregister, and toggle functions', () => {
|
||||
mountComposable(() => {
|
||||
useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
expect(providedFunctions).not.toBe(undefined);
|
||||
expect(providedFunctions.register).toBeInstanceOf(Function);
|
||||
expect(providedFunctions.unregister).toBeInstanceOf(Function);
|
||||
expect(providedFunctions.toggle).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
||||
it('Adds the registered item to the items array', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
expect(result.items.value).toEqual([testItem]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Preselects the first item on first load if mandatory is set', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, { mandatory: ref(true) });
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
expect(result.items.value).toEqual([testItem]);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Removes the unregistered item from the items array', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
result.items.value = [testItem];
|
||||
|
||||
providedFunctions.unregister(testItem);
|
||||
expect(result.items.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Passed in state', () => {
|
||||
it('Defaults to internal selection state if no state is provided', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
result._selection.value = [0];
|
||||
expect(result.selection.value).toEqual([0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Uses passed in state if provided', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent({
|
||||
selection: ref([0]),
|
||||
});
|
||||
|
||||
expect(result.selection.value).toEqual([0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls provided onSelectionChange handler on selection changes', () => {
|
||||
const onSelectionChange = jest.fn();
|
||||
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent({
|
||||
onSelectionChange: onSelectionChange,
|
||||
});
|
||||
|
||||
result.selection.value = [0];
|
||||
});
|
||||
|
||||
expect(onSelectionChange).toHaveBeenCalledWith([0]);
|
||||
});
|
||||
|
||||
it('Sets the internal selection state on selection changes', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
result.selection.value = [0];
|
||||
|
||||
expect(result._selection.value).toEqual([0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Selections', () => {
|
||||
it('Toggles child items on and off when the toggle function is called', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('Does not toggle the item off if mandatory is enabled', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, { mandatory: ref(true) });
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Only allows one active item at a time', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Allows multiple items if multiple flag is set', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, { multiple: ref(true) });
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Deselects individual items on toggle', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, { multiple: ref(true) });
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Stops adding more items if max value is reached', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, {
|
||||
max: ref(2),
|
||||
multiple: ref(true),
|
||||
});
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
const testItem3 = {
|
||||
active: ref(false),
|
||||
value: 'test3',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.register(testItem3);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
providedFunctions.toggle(testItem3);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Disregards max option is option is set to -1', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, {
|
||||
multiple: ref(true),
|
||||
max: ref(-1),
|
||||
});
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('Does not let you remove the last item if multiple and mandatory is set', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent(undefined, {
|
||||
mandatory: ref(true),
|
||||
multiple: ref(true),
|
||||
});
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
const testItem3 = {
|
||||
active: ref(false),
|
||||
value: 'test3',
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.register(testItem3);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
providedFunctions.toggle(testItem2);
|
||||
expect(result.selection.value).toEqual(['test', 'test2']);
|
||||
providedFunctions.toggle(testItem3);
|
||||
expect(result.selection.value).toEqual(['test', 'test2', 'test3']);
|
||||
providedFunctions.toggle(testItem3);
|
||||
providedFunctions.toggle(testItem2);
|
||||
providedFunctions.toggle(testItem);
|
||||
expect(result.selection.value).toEqual(['test']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateChildren', () => {
|
||||
it('Sets the children item states based on selection', () => {
|
||||
mountComposable(async () => {
|
||||
const result: any = useGroupableParent();
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
const testItem3 = {
|
||||
active: ref(true),
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.register(testItem3);
|
||||
|
||||
result.selection.value = ['test', 2];
|
||||
await Vue.nextTick(); // waits for the watch handler to kick in
|
||||
expect(testItem.active.value).toBe(true);
|
||||
expect(testItem2.active.value).toBe(false);
|
||||
expect(testItem3.active.value).toBe(true);
|
||||
|
||||
result.selection.value = [];
|
||||
await Vue.nextTick();
|
||||
expect(testItem.active.value).toBe(false);
|
||||
expect(testItem2.active.value).toBe(false);
|
||||
expect(testItem3.active.value).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Extracts the right value for a given item', () => {
|
||||
mountComposable(() => {
|
||||
const result: any = useGroupableParent();
|
||||
|
||||
const providedFunctions: any = inject('item-group');
|
||||
|
||||
const testItem = {
|
||||
active: ref(false),
|
||||
value: 'test',
|
||||
};
|
||||
|
||||
const testItem2 = {
|
||||
active: ref(false),
|
||||
value: 'test2',
|
||||
};
|
||||
|
||||
const testItem3 = {
|
||||
active: ref(false),
|
||||
};
|
||||
|
||||
providedFunctions.register(testItem);
|
||||
providedFunctions.register(testItem2);
|
||||
providedFunctions.register(testItem3);
|
||||
|
||||
expect(result.getValueForItem(testItem2)).toBe('test2');
|
||||
expect(result.getValueForItem(testItem3)).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
import useSizeClass from './size-class';
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
|
||||
describe('Composables / Size Class', () => {
|
||||
it('Extracts the correct class based on given props', () => {
|
||||
let props = {
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: false,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe(null);
|
||||
}).destroy();
|
||||
|
||||
props = {
|
||||
xSmall: true,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: false,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe('x-small');
|
||||
}).destroy();
|
||||
|
||||
props = {
|
||||
xSmall: false,
|
||||
small: true,
|
||||
large: false,
|
||||
xLarge: false,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe('small');
|
||||
}).destroy();
|
||||
|
||||
props = {
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: true,
|
||||
xLarge: false,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe('large');
|
||||
}).destroy();
|
||||
|
||||
props = {
|
||||
xSmall: false,
|
||||
small: false,
|
||||
large: false,
|
||||
xLarge: true,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe('x-large');
|
||||
}).destroy();
|
||||
});
|
||||
|
||||
it('Defaults to the smallest size if multiple sizes are passed', () => {
|
||||
const props = {
|
||||
xSmall: false,
|
||||
small: true,
|
||||
large: true,
|
||||
xLarge: true,
|
||||
ignoredKey: 'test',
|
||||
};
|
||||
|
||||
mountComposable(() => {
|
||||
const className = useSizeClass(props);
|
||||
expect(className.value).toBe('small');
|
||||
}).destroy();
|
||||
});
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
import useElementSize from './use-element-size';
|
||||
import { ResizeObserver } from 'resize-observer';
|
||||
import { ref } from '@vue/composition-api';
|
||||
|
||||
jest.mock('resize-observer');
|
||||
|
||||
const mockResizeObserver = {
|
||||
observe: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Composables / useElementSize', () => {
|
||||
beforeEach(() => {
|
||||
(ResizeObserver as jest.Mock).mockImplementation(() => {
|
||||
return mockResizeObserver;
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a resize observer', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
mountComposable(() => {
|
||||
useElementSize(el);
|
||||
});
|
||||
|
||||
expect(ResizeObserver).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls observe with the passed element on mount', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
mountComposable(() => {
|
||||
useElementSize(el);
|
||||
});
|
||||
|
||||
expect(mockResizeObserver.observe).toHaveBeenCalledWith(el);
|
||||
});
|
||||
|
||||
it('Calls observer with element if ref is passed', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
mountComposable(() => {
|
||||
const refEl = ref<Element>(el);
|
||||
useElementSize(refEl);
|
||||
});
|
||||
|
||||
expect(mockResizeObserver.observe).toHaveBeenCalledWith(el);
|
||||
});
|
||||
|
||||
it('Does not call observe when passed element is null or undefined', () => {
|
||||
mountComposable(() => {
|
||||
useElementSize(ref(null));
|
||||
});
|
||||
|
||||
expect(mockResizeObserver.observe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls disconnect on unmount', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
mountComposable(() => {
|
||||
useElementSize(el);
|
||||
}).destroy();
|
||||
|
||||
expect(mockResizeObserver.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Sets the returned width and height refs on ResizeObserver handler', () => {
|
||||
let handler: (_: any) => void;
|
||||
|
||||
(ResizeObserver as jest.Mock).mockImplementation((constructorParam) => {
|
||||
handler = constructorParam;
|
||||
return mockResizeObserver;
|
||||
});
|
||||
|
||||
const el = document.createElement('div');
|
||||
|
||||
mountComposable(() => {
|
||||
const { width, height } = useElementSize(el);
|
||||
expect(width.value).toBe(0);
|
||||
expect(height.value).toBe(0);
|
||||
|
||||
handler([
|
||||
{
|
||||
contentRect: {
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(width.value).toBe(150);
|
||||
expect(height.value).toBe(150);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import { ref } from '@vue/composition-api';
|
||||
import useEventListener from './use-event-listener';
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
|
||||
describe('Composables / Event Listener', () => {
|
||||
it('Adds passed event listener onMounted', () => {
|
||||
const map: any = {};
|
||||
|
||||
window.addEventListener = jest.fn((event, cb) => {
|
||||
map[event] = cb;
|
||||
});
|
||||
|
||||
window.removeEventListener = jest.fn((event) => {
|
||||
delete map[event];
|
||||
});
|
||||
|
||||
const handler = () => {};
|
||||
|
||||
const component = mountComposable(() => {
|
||||
useEventListener(window, 'keydown', handler);
|
||||
});
|
||||
|
||||
expect(map.keydown).toBe(handler);
|
||||
|
||||
component.destroy();
|
||||
|
||||
expect(map.keydown).toBe(undefined);
|
||||
});
|
||||
|
||||
it('Uses the value if the target is a ref', () => {
|
||||
const target = ref(window);
|
||||
const map: any = {};
|
||||
|
||||
window.addEventListener = jest.fn((event, cb) => {
|
||||
map[event] = cb;
|
||||
});
|
||||
|
||||
window.removeEventListener = jest.fn((event) => {
|
||||
delete map[event];
|
||||
});
|
||||
|
||||
const handler = () => {};
|
||||
|
||||
const component = mountComposable(() => {
|
||||
useEventListener(target, 'keydown', handler);
|
||||
});
|
||||
|
||||
expect(map.keydown).toBe(handler);
|
||||
|
||||
component.destroy();
|
||||
|
||||
expect(map.keydown).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI, { defineComponent, ref, onMounted } from '@vue/composition-api';
|
||||
import useScrollDistance from './use-scroll-distance';
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
import Vue from 'vue';
|
||||
import VSheet from '@/components/v-sheet';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Composables / useScrollDistance', () => {
|
||||
it('Returns the correct scroll position', () => {
|
||||
const TestComponent = defineComponent({
|
||||
setup() {
|
||||
const el = ref<Element | null>(null);
|
||||
|
||||
const { top, left } = useScrollDistance(el);
|
||||
|
||||
onMounted(() => {
|
||||
el.value!.scrollTop = 150;
|
||||
el.value!.scrollLeft = 200;
|
||||
el.value!.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(top.value).toBe(150);
|
||||
expect(left.value).toBe(200);
|
||||
});
|
||||
|
||||
return { el };
|
||||
},
|
||||
template: `
|
||||
<div ref="el" style="max-width: 150px; max-height: 150px; overflow: auto;">
|
||||
<div style="width: 600px; height: 600px;" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
shallowMount(TestComponent, { localVue });
|
||||
});
|
||||
|
||||
it('Supports elements or refs for param', () => {
|
||||
mountComposable(() => {
|
||||
const testEl = null;
|
||||
const testVal = ref(testEl);
|
||||
const result = useScrollDistance(testVal);
|
||||
expect(result.target.value).toBe(null);
|
||||
}).destroy();
|
||||
|
||||
mountComposable(() => {
|
||||
const testEl = document.createElement('div');
|
||||
const testVal = testEl;
|
||||
const result = useScrollDistance(testVal);
|
||||
expect(result.target.value).toBe(testEl);
|
||||
}).destroy();
|
||||
|
||||
mountComposable(() => {
|
||||
const testEl = document.createElement('div');
|
||||
const testVal = ref(testEl);
|
||||
const result = useScrollDistance(testVal);
|
||||
expect(result.target.value).toBe(testEl);
|
||||
}).destroy();
|
||||
|
||||
const TestComponent = defineComponent({
|
||||
components: { VSheet },
|
||||
setup() {
|
||||
const el = ref<Vue | null>(null);
|
||||
const { target } = useScrollDistance(el as any);
|
||||
|
||||
onMounted(() => {
|
||||
expect(target.value instanceof HTMLElement).toBe(true);
|
||||
});
|
||||
|
||||
return { el };
|
||||
},
|
||||
template: `
|
||||
<v-sheet ref="el" style="max-width: 150px; max-height: 150px; overflow: auto;">
|
||||
<div style="width: 600px; height: 600px;" />
|
||||
</v-sheet>
|
||||
`,
|
||||
});
|
||||
|
||||
shallowMount(TestComponent, { localVue }).destroy();
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
import { watch } from '@vue/composition-api';
|
||||
import useWindowSize from './use-window-size';
|
||||
import mountComposable from '../../../.jest/mount-composable';
|
||||
|
||||
describe('Composables / Window Size', () => {
|
||||
it('Adds passed event listener onMounted', async () => {
|
||||
let testWidth = 0;
|
||||
|
||||
const component = mountComposable(() => {
|
||||
const { width } = useWindowSize();
|
||||
|
||||
watch(width, (val: number) => (testWidth = val));
|
||||
});
|
||||
|
||||
expect(testWidth).toBe(0);
|
||||
|
||||
// @ts-ignore
|
||||
window.innerWidth = 1024;
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(testWidth).toBe(1024);
|
||||
});
|
||||
|
||||
it('Adds / removes resize event handler on mount / unmount', async () => {
|
||||
const map: any = {};
|
||||
|
||||
window.addEventListener = jest.fn((event, cb) => {
|
||||
map[event] = cb;
|
||||
});
|
||||
|
||||
window.removeEventListener = jest.fn((event) => {
|
||||
delete map[event];
|
||||
});
|
||||
|
||||
const component = mountComposable(() => {
|
||||
useWindowSize();
|
||||
});
|
||||
|
||||
expect(map.resize).toBeTruthy();
|
||||
|
||||
component.destroy();
|
||||
|
||||
expect(map.keydown).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { processValue } from './click-outside';
|
||||
|
||||
describe('Directives / Click Outside', () => {
|
||||
describe('processValue', () => {
|
||||
it('Uses passed function as handler', () => {
|
||||
const mockFn = () => {};
|
||||
const value = processValue(mockFn);
|
||||
expect(value.handler).toBe(mockFn);
|
||||
});
|
||||
|
||||
it('Uses passed options as value', () => {
|
||||
const mockHandlerFn = () => {};
|
||||
const mockMiddlewareFn = () => true;
|
||||
const mockOptions = {
|
||||
handler: mockHandlerFn,
|
||||
middleware: mockMiddlewareFn,
|
||||
events: ['test'],
|
||||
disabled: true,
|
||||
};
|
||||
const value = processValue(mockOptions);
|
||||
expect(value.handler).toBe(mockHandlerFn);
|
||||
expect(value.middleware).toBe(mockMiddlewareFn);
|
||||
expect(value.events).toEqual(['test']);
|
||||
expect(value.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('Uses default values if options are missing', () => {
|
||||
const mockOptions = {};
|
||||
const value = processValue(mockOptions);
|
||||
expect(value.handler).toBeInstanceOf(Function);
|
||||
expect(value.middleware).toBeInstanceOf(Function);
|
||||
expect(value.events).toEqual(['pointerdown']);
|
||||
expect(value.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import Focus from './focus';
|
||||
|
||||
describe('Directives / Focus', () => {
|
||||
it('Calls focus() on the element if binding is truthy', () => {
|
||||
const el = { focus: jest.fn() };
|
||||
Focus.inserted!(el as any, { value: true } as any, null as any, null as any);
|
||||
expect(el.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls blur() on the element if binding is false', () => {
|
||||
const el = { blur: jest.fn() };
|
||||
Focus.inserted!(el as any, { value: false } as any, null as any, null as any);
|
||||
expect(el.blur).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceDivider from './divider.vue';
|
||||
|
||||
import VDivider from '@/components/v-divider';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-divider', VDivider);
|
||||
|
||||
describe('Interfaces / Numeric', () => {
|
||||
it('Renders a v-input', () => {
|
||||
const component = shallowMount(InterfaceDivider, {
|
||||
localVue,
|
||||
});
|
||||
expect(component.find(VDivider).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VNotice from '@/components/v-notice';
|
||||
import VSelect from '@/components/v-select';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import InterfaceDropdownMultiselect from './dropdown-multiselect.vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('v-select', VSelect);
|
||||
localVue.component('v-notice', VNotice);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Interfaces / Dropdown (Multiselect)', () => {
|
||||
it('Renders a notice when choices arent set', async () => {
|
||||
const component = shallowMount(InterfaceDropdownMultiselect, {
|
||||
localVue,
|
||||
i18n,
|
||||
listeners: {
|
||||
input: () => undefined,
|
||||
},
|
||||
});
|
||||
expect(component.find(VNotice).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Renders select when choices exist', async () => {
|
||||
const component = shallowMount(InterfaceDropdownMultiselect, {
|
||||
localVue,
|
||||
i18n,
|
||||
listeners: {
|
||||
input: () => undefined,
|
||||
},
|
||||
propsData: {
|
||||
choices: `
|
||||
test
|
||||
`,
|
||||
},
|
||||
});
|
||||
expect(component.find(VSelect).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VNotice from '@/components/v-notice';
|
||||
import VSelect from '@/components/v-select';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import InterfaceDropdown from './dropdown.vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('v-select', VSelect);
|
||||
localVue.component('v-notice', VNotice);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Interfaces / Dropdown', () => {
|
||||
it('Renders a notice when choices arent set', async () => {
|
||||
const component = shallowMount(InterfaceDropdown, {
|
||||
localVue,
|
||||
i18n,
|
||||
listeners: {
|
||||
input: () => undefined,
|
||||
},
|
||||
});
|
||||
expect(component.find(VNotice).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Renders select when choices exist', async () => {
|
||||
const component = shallowMount(InterfaceDropdown, {
|
||||
localVue,
|
||||
i18n,
|
||||
listeners: {
|
||||
input: () => undefined,
|
||||
},
|
||||
propsData: {
|
||||
choices: `
|
||||
test
|
||||
`,
|
||||
},
|
||||
});
|
||||
expect(component.find(VSelect).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceIcon from './icon.vue';
|
||||
import ClickOutside from '@/directives/click-outside/';
|
||||
import Tooltip from '@/directives/tooltip/tooltip';
|
||||
import Focus from '@/directives/focus/focus';
|
||||
import TransitionExpand from '@/components/transition/expand';
|
||||
|
||||
import VInput from '@/components/v-input';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import VMenu from '@/components/v-menu';
|
||||
import VDivider from '@/components/v-divider';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.component('v-input', VInput);
|
||||
localVue.component('v-menu', VMenu);
|
||||
localVue.directive('click-outside', ClickOutside);
|
||||
localVue.directive('tooltip', Tooltip);
|
||||
localVue.directive('focus', Focus);
|
||||
localVue.component('transition-expand', TransitionExpand);
|
||||
localVue.component('v-divider', VDivider);
|
||||
|
||||
describe('Interfaces / Icon', () => {
|
||||
it('Renders a v-icon', async () => {
|
||||
const component = mount(InterfaceIcon, {
|
||||
localVue,
|
||||
mocks: {
|
||||
$t: jest.fn(),
|
||||
},
|
||||
});
|
||||
expect(component.find(VMenu).exists()).toBe(true);
|
||||
component.find(VInput).find('input').setValue('search');
|
||||
await component.vm.$nextTick();
|
||||
expect(component.find(VIcon).text()).toBe('search');
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceMarkdown from './markdown.vue';
|
||||
|
||||
import VTextarea from '@/components/v-textarea';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-textarea', VTextarea);
|
||||
|
||||
describe('Interfaces / Markdown', () => {
|
||||
it('Renders a v-markdown', () => {
|
||||
const component = shallowMount(InterfaceMarkdown, {
|
||||
localVue,
|
||||
propsData: {
|
||||
placeholder: 'Enter value...',
|
||||
},
|
||||
listeners: {
|
||||
input: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.find(VTextarea).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceNumeric from './numeric.vue';
|
||||
|
||||
import VInput from '@/components/v-input';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-input', VInput);
|
||||
|
||||
describe('Interfaces / Numeric', () => {
|
||||
it('Renders a v-input', () => {
|
||||
const component = shallowMount(InterfaceNumeric, {
|
||||
localVue,
|
||||
propsData: {},
|
||||
listeners: {
|
||||
input: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.find(VInput).exists()).toBe(true);
|
||||
expect(component.find(VInput).attributes().type).toBe('number');
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceSlider from './slider.vue';
|
||||
|
||||
import VSlider from '@/components/v-slider';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-slider', VSlider);
|
||||
|
||||
describe('Interfaces / Slider', () => {
|
||||
it('Renders a v-slider', () => {
|
||||
const component = shallowMount(InterfaceSlider, {
|
||||
localVue,
|
||||
propsData: {},
|
||||
listeners: {
|
||||
input: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.find(VSlider).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceTextInput from './text-input.vue';
|
||||
|
||||
import VInput from '@/components/v-input';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-input', VInput);
|
||||
|
||||
describe('Interfaces / Text Input', () => {
|
||||
it('Renders a v-input', () => {
|
||||
const component = shallowMount(InterfaceTextInput, {
|
||||
localVue,
|
||||
propsData: {
|
||||
trim: false,
|
||||
placeholder: 'Enter value...',
|
||||
},
|
||||
listeners: {
|
||||
input: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.find(VInput).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import InterfaceTextarea from './textarea.vue';
|
||||
|
||||
import VTextarea from '@/components/v-textarea';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-textarea', VTextarea);
|
||||
|
||||
describe('Interfaces / Text Input', () => {
|
||||
it('Renders a v-textarea', () => {
|
||||
const component = shallowMount(InterfaceTextarea, {
|
||||
localVue,
|
||||
propsData: {
|
||||
placeholder: 'Enter value...',
|
||||
},
|
||||
listeners: {
|
||||
input: () => {},
|
||||
},
|
||||
});
|
||||
expect(component.find(VTextarea).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import InterfaceToggle from './toggle.vue';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VCheckbox from '@/components/v-checkbox';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-checkbox', VCheckbox);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Interfaces / Toggle', () => {
|
||||
it('Renders a v-checkbox', () => {
|
||||
const component = shallowMount(InterfaceToggle, {
|
||||
localVue,
|
||||
listeners: {
|
||||
input: () => undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find(VCheckbox).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as lang from './index';
|
||||
|
||||
describe('i18n / setLanguage', () => {
|
||||
it('Returns false if invalid language is passed', async () => {
|
||||
// @ts-ignore
|
||||
const result = await lang.setLanguage('abc');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns true if given language is current language', async () => {
|
||||
const result = await lang.setLanguage('en-US');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns true on a successful import', async () => {
|
||||
const result = await lang.setLanguage('nl-NL');
|
||||
// TODO I would like to figure out how to mock the dynamic import in here, so we can
|
||||
// test if it's actually fetching the correct files in the import
|
||||
expect(result).toBe(true);
|
||||
expect(lang.i18n.locale).toBe('nl-NL');
|
||||
});
|
||||
|
||||
it('Immediately sets language if locale is already loaded', async () => {
|
||||
lang.loadedLanguages.push('de-DE');
|
||||
const result = await lang.setLanguage('de-DE');
|
||||
expect(result).toBe(true);
|
||||
expect(lang.i18n.locale).toBe('de-DE');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
@@ -30,7 +30,7 @@ export const types = [
|
||||
export const localTypes = ['standard', 'file', 'files', 'm2o', 'o2m', 'm2m', 'presentation', 'translations'] as const;
|
||||
|
||||
export type FieldSchema = {
|
||||
/** @todo import this from knex-schema-inspector when that's launched */
|
||||
/** @todo import this from @directus/schema when that's launched */
|
||||
name: string;
|
||||
table: string;
|
||||
type: string;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import arraysAreEqual from './arrays-are-equal';
|
||||
|
||||
describe('Util / arraysAreEqual', () => {
|
||||
it('Returns true for equal arrays', () => {
|
||||
const a1 = ['a', 'b', 'c'];
|
||||
const a2 = ['a', 'b', 'c'];
|
||||
const result = arraysAreEqual(a1, a2);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns true for equal arrays in different orders', () => {
|
||||
const a1 = ['a', 'b', 'c'];
|
||||
const a2 = ['c', 'a', 'b'];
|
||||
const result = arraysAreEqual(a1, a2);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns false for inequal arrays in different orders', () => {
|
||||
const a1 = ['a', 'b', 'c'];
|
||||
const a2 = [1, 2, 3];
|
||||
const result = arraysAreEqual(a1, a2);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns false for equal arrays in inequal types', () => {
|
||||
const a1 = ['1', '2', '3'];
|
||||
const a2 = [1, 2, 3];
|
||||
const result = arraysAreEqual(a1, a2);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns false for arrays of different lenghts', () => {
|
||||
const a1 = ['a', 'b', 'c', 'd'];
|
||||
const a2 = ['a', 'b', 'c'];
|
||||
const result = arraysAreEqual(a1, a2);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import capitalizeFirst from './capitalize-first';
|
||||
|
||||
describe('Utils / capitalizeFirst', () => {
|
||||
it('Capitalizes the first letter', () => {
|
||||
const testCases = [
|
||||
['test', 'Test'],
|
||||
['directus', 'Directus'],
|
||||
['123', '123'],
|
||||
['_abc', '_abc'],
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
expect(capitalizeFirst(testCase[0])).toBe(testCase[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import getRootPath from './get-root-path';
|
||||
|
||||
describe('Utils / get root path', () => {
|
||||
it('Calculates the correct API root URL based on window', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
pathname: '/api/nested/admin',
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const result = getRootPath();
|
||||
expect(result).toBe('/api/nested/');
|
||||
});
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import { isEmpty, notEmpty } from './is-empty';
|
||||
|
||||
describe('Util / isEmpty', () => {
|
||||
describe('isEmpty', () => {
|
||||
it('Returns true if value is null or undefined', () => {
|
||||
expect(isEmpty(null)).toBe(true);
|
||||
expect(isEmpty(undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns false if value is not null or undefined', () => {
|
||||
expect(isEmpty('test')).toBe(false);
|
||||
expect(isEmpty(123)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('notEmpty', () => {
|
||||
it('Returns true if value is null or undefined', () => {
|
||||
expect(notEmpty(null)).toBe(false);
|
||||
expect(notEmpty(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('Returns true if value is not null or undefined', () => {
|
||||
expect(notEmpty('test')).toBe(true);
|
||||
expect(notEmpty(123)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import Vue, { Component } from 'vue';
|
||||
import registerComponent from './register-component';
|
||||
|
||||
describe('Utils / Register Component', () => {
|
||||
it('Calls Vue.component with the given arguments', () => {
|
||||
const spy = jest.spyOn(Vue, 'component');
|
||||
const component: Component = {
|
||||
render(h) {
|
||||
return h('div');
|
||||
},
|
||||
};
|
||||
registerComponent('test', component);
|
||||
expect(spy).toHaveBeenCalledWith('test', component);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import translateShortcut from './translate-shortcut';
|
||||
|
||||
describe('Utils / Translate Shortcut', () => {
|
||||
it('Shows capitalized + separated on Windows', () => {
|
||||
Object.defineProperty(window.navigator, 'platform', { value: 'Win32', writable: true });
|
||||
|
||||
expect(translateShortcut(['meta', 'shift', 's'])).toBe('Ctrl+Shift+S');
|
||||
});
|
||||
|
||||
it('Shows Mac keys on Mac', () => {
|
||||
Object.defineProperty(window.navigator, 'platform', { value: 'Mac-Intel', writable: true });
|
||||
|
||||
expect(translateShortcut(['meta', 'shift', 's'])).toBe('⌘⇧S');
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import HeaderBarActions from './header-bar-actions.vue';
|
||||
|
||||
import VButton from '@/components/v-button';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Views / Private / Header Bar Actions', () => {
|
||||
it('Renders', () => {
|
||||
const component = mount(HeaderBarActions, {
|
||||
localVue,
|
||||
});
|
||||
|
||||
expect(component.isVueInstance()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import HeaderBar from './header-bar.vue';
|
||||
import PortalVue from 'portal-vue';
|
||||
|
||||
import VButton from '@/components/v-button';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(PortalVue);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Views / Private / Header Bar', () => {
|
||||
const observeMock = {
|
||||
observe: () => null,
|
||||
disconnect: () => null, // maybe not needed
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(window as any).IntersectionObserver = jest.fn(() => observeMock);
|
||||
});
|
||||
|
||||
it('Emits toggle event when toggle buttons are clicked', () => {
|
||||
const component = mount(HeaderBar, {
|
||||
localVue,
|
||||
propsData: {
|
||||
title: 'Title',
|
||||
},
|
||||
});
|
||||
|
||||
const navToggle = component.find('.nav-toggle > .button');
|
||||
navToggle.trigger('click');
|
||||
|
||||
expect(component.emitted('toggle:nav')?.[0]).toBeTruthy();
|
||||
|
||||
const sidebarToggle = component.find('.sidebar-toggle > .button');
|
||||
sidebarToggle.trigger('click');
|
||||
|
||||
expect(component.emitted('toggle:sidebar')?.[0]).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import NotificationItem from './notification-item.vue';
|
||||
import { useNotificationsStore } from '@/stores/';
|
||||
import VIcon from '@/components/v-icon';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Views / Private / Components / Notification Item', () => {
|
||||
it('Calls remove with id on close click if persist is enabled', () => {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
jest.spyOn(notificationsStore as any, 'remove');
|
||||
const component = mount(NotificationItem, {
|
||||
localVue,
|
||||
propsData: {
|
||||
id: '123',
|
||||
title: 'Test',
|
||||
persist: true,
|
||||
showClose: true,
|
||||
},
|
||||
});
|
||||
|
||||
component.find('.close').trigger('click');
|
||||
expect(notificationsStore.remove).toHaveBeenCalledWith('123');
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import SaveOptions from './save-options.vue';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { i18n } from '@/lang';
|
||||
|
||||
import VList, { VListItem, VListItemIcon, VListItemContent, VListItemHint } from '@/components/v-list/';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import VMenu from '@/components/v-menu';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-list', VList);
|
||||
localVue.component('v-list-item', VListItem);
|
||||
localVue.component('v-list-item-icon', VListItemIcon);
|
||||
localVue.component('v-list-item-content', VListItemContent);
|
||||
localVue.component('v-list-item-hint', VListItemHint);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.component('v-menu', VMenu);
|
||||
|
||||
describe('Views / Private / Components / Save Options', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(SaveOptions, {
|
||||
localVue,
|
||||
i18n,
|
||||
});
|
||||
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
import SidebarButton from './sidebar-button.vue';
|
||||
import { useAppStore } from '@/stores';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Views / Private / Components / Sidebar Button', () => {
|
||||
it('Does not render the title when the sidebar is closed', () => {
|
||||
const appStore = useAppStore();
|
||||
|
||||
appStore.state.sidebarOpen = false;
|
||||
const component = shallowMount(SidebarButton, {
|
||||
localVue,
|
||||
});
|
||||
|
||||
expect(component.find('.title').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import SidebarDetailGroup from './sidebar-detail-group.vue';
|
||||
import VItemGroup from '@/components/v-item-group';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-item-group', VItemGroup);
|
||||
|
||||
describe('Views / Private / Sidebar Detail Group', () => {
|
||||
it('Resets the opened child details when the sidebar closes', async () => {
|
||||
const component = shallowMount(SidebarDetailGroup, {
|
||||
localVue,
|
||||
propsData: {
|
||||
sidebarOpen: true,
|
||||
},
|
||||
});
|
||||
|
||||
(component.vm as any).openDetail = ['test'];
|
||||
|
||||
component.setProps({ sidebarOpen: false });
|
||||
await component.vm.$nextTick();
|
||||
expect((component.vm as any).openDetail).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI, { ref } from '@vue/composition-api';
|
||||
import SidebarDetail from './sidebar-detail.vue';
|
||||
import * as GroupableComposable from '@/composables/groupable/groupable';
|
||||
import VIcon from '@/components/v-icon';
|
||||
import VDivider from '@/components/v-divider';
|
||||
import TransitionExpand from '@/components/transition/expand';
|
||||
import VBadge from '@/components/v-badge/';
|
||||
import { useAppStore } from '@/stores';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.component('transition-expand', TransitionExpand);
|
||||
localVue.component('v-divider', VDivider);
|
||||
localVue.component('v-badge', VBadge);
|
||||
|
||||
describe('Sidebar Detail', () => {
|
||||
it('Uses the useGroupable composition', () => {
|
||||
jest.spyOn(GroupableComposable, 'useGroupable');
|
||||
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.sidebarOpen = false;
|
||||
|
||||
mount(SidebarDetail, {
|
||||
localVue,
|
||||
propsData: {
|
||||
icon: 'person',
|
||||
title: 'Users',
|
||||
},
|
||||
provide: {
|
||||
'item-group': {
|
||||
register: () => {},
|
||||
unregister: () => {},
|
||||
toggle: () => {},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(GroupableComposable.useGroupable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Passes the title prop as selection value', () => {
|
||||
jest.spyOn(GroupableComposable, 'useGroupable');
|
||||
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.sidebarOpen = false;
|
||||
|
||||
mount(SidebarDetail, {
|
||||
localVue,
|
||||
propsData: {
|
||||
icon: 'person',
|
||||
title: 'Users',
|
||||
},
|
||||
provide: {
|
||||
'item-group': {
|
||||
register: () => {},
|
||||
unregister: () => {},
|
||||
toggle: () => {},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(GroupableComposable.useGroupable).toHaveBeenCalledWith('Users', 'sidebar-detail');
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import PrivateView from './private-view.vue';
|
||||
import VOverlay from '@/components/v-overlay';
|
||||
import VProgressCircular from '@/components/v-progress/circular';
|
||||
import PortalVue from 'portal-vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import { i18n } from '@/lang';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(PortalVue);
|
||||
localVue.use(VueI18n);
|
||||
localVue.component('v-overlay', VOverlay);
|
||||
localVue.component('v-progress-circular', VProgressCircular);
|
||||
|
||||
describe('Views / Private', () => {
|
||||
it('Adds the is-open class to the nav', async () => {
|
||||
const component = shallowMount(PrivateView, {
|
||||
localVue,
|
||||
i18n,
|
||||
propsData: {
|
||||
title: 'Title',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.navigation').classes()).toEqual(['navigation']);
|
||||
|
||||
(component.vm as any).navOpen = true;
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.navigation').classes()).toEqual(['navigation', 'is-open']);
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env", "jest"],
|
||||
"types": ["webpack-env"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
|
||||
41189
package-lock.json
generated
41189
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -3,8 +3,10 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "lerna run dev --stream --parallel",
|
||||
"build": "lerna run build",
|
||||
"release": "lerna publish --force-publish",
|
||||
"cli": "cross-env NODE_ENV=development ts-node --script-mode --transpile-only api/src/cli/index.ts"
|
||||
"cli": "cross-env NODE_ENV=development ts-node --script-mode --transpile-only api/src/cli/index.ts",
|
||||
"postinstall": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,6 +35,7 @@
|
||||
"@types/base-64": "^0.1.3",
|
||||
"@types/busboy": "^0.2.3",
|
||||
"@types/bytes": "^3.1.0",
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/clear": "^0.1.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/cors": "^2.8.7",
|
||||
@@ -43,7 +46,6 @@
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/highlight.js": "^9.12.4",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/joi": "^14.3.4",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/json2csv": "^5.0.1",
|
||||
@@ -52,6 +54,7 @@
|
||||
"@types/lodash": "^4.14.159",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
@@ -69,7 +72,6 @@
|
||||
"@vue/cli-plugin-eslint": "^4.5.6",
|
||||
"@vue/cli-plugin-router": "^4.5.6",
|
||||
"@vue/cli-plugin-typescript": "^4.5.6",
|
||||
"@vue/cli-plugin-unit-jest": "^4.5.6",
|
||||
"@vue/cli-plugin-vuex": "^4.5.6",
|
||||
"@vue/cli-service": "^4.5.6",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
@@ -90,9 +92,6 @@
|
||||
"fs-extra": "^9.0.1",
|
||||
"html-loader": "^1.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.4.2",
|
||||
"jest-config": "^26.4.2",
|
||||
"jest-sonar": "^0.2.10",
|
||||
"lerna": "^3.22.1",
|
||||
"lint-staged": "^10.3.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
@@ -121,7 +120,6 @@
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"swagger-cli": "^4.0.4",
|
||||
"swagger-ui-watcher": "^2.1.11",
|
||||
"ts-jest": "^26.2.0",
|
||||
"ts-node": "^8.10.2",
|
||||
"ts-node-dev": "^1.0.0-pre.63",
|
||||
"tslint": "^6.1.3",
|
||||
@@ -139,7 +137,7 @@
|
||||
"@directus/app": "file:app",
|
||||
"@directus/docs": "file:docs",
|
||||
"@directus/format-title": "file:packages/format-title",
|
||||
"@directus/specs": "file:packages/spec",
|
||||
"@directus/specs": "file:packages/specs",
|
||||
"create-directus-project": "file:packages/create-directus-project",
|
||||
"directus": "file:api"
|
||||
},
|
||||
|
||||
@@ -29,39 +29,9 @@
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||
"start": "rollup -c rollup.config.ts -w",
|
||||
"test": "jest --coverage",
|
||||
"test:watch": "jest --coverage --watch",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".(ts|tsx)": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/test/"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 90,
|
||||
"functions": 95,
|
||||
"lines": 95,
|
||||
"statements": 95
|
||||
}
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"src/*.{js,ts}"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.33.1",
|
||||
"typescript": "^4.0.5"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import formatTitle from '../src/format-title';
|
||||
|
||||
const testStrings = [
|
||||
['the_new_iphone_comes_out_in_october', 'The New iPhone Comes out in October'],
|
||||
['i-like-watching-youtube', 'I like Watching YouTube'],
|
||||
['thumbnailerTTL', 'Thumbnailer TTL'],
|
||||
['youtubeApi', 'YouTube API'],
|
||||
['thumbnail-cache-ttl', 'Thumbnail Cache TTL'],
|
||||
[
|
||||
'This package is pretty useful though not often used',
|
||||
'This Package Is Pretty Useful though Not Often Used',
|
||||
],
|
||||
['auto_sign_out', 'Auto Sign Out'],
|
||||
['edited_on', 'Edited On'],
|
||||
['actionBy', 'Action By'],
|
||||
['app_url', 'App URL'],
|
||||
['2fa_secret', '2FA Secret'],
|
||||
];
|
||||
|
||||
describe('Title Formatter', () => {
|
||||
testStrings.forEach(([input, output]) => {
|
||||
test(`${input} => ${output}`, () => {
|
||||
expect(formatTitle(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
it('Accepts custom separator regex', () => {
|
||||
expect(formatTitle('hello+world', /\+/g)).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@
|
||||
"name": "@directus/schema",
|
||||
"version": "0.0.25",
|
||||
"description": "Utility for extracting information about existing DB schema",
|
||||
"main": "dist/lib/index.js",
|
||||
"types": "dist/lib/index.d.ts",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsc --build",
|
||||
"prepare": "npm run build",
|
||||
"lint": "prettier --check .",
|
||||
"test": "npm run lint && ts-mocha test/**/*.spec.ts"
|
||||
@@ -30,25 +30,9 @@
|
||||
},
|
||||
"homepage": "https://github.com/directus/next#readme",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/lodash.flatten": "^4.4.6",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^14.0.13",
|
||||
"chai": "^4.2.0",
|
||||
"husky": "^4.2.5",
|
||||
"knex": "^0.21.1",
|
||||
"lint-staged": "^10.2.11",
|
||||
"mocha": "^8.2.0",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.4.0",
|
||||
"prettier": "^2.0.5",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"knex": "^0.21.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash.flatten": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,193 +1,180 @@
|
||||
import Knex from 'knex';
|
||||
import flatten from 'lodash.flatten';
|
||||
import { flatten } from 'lodash';
|
||||
import { Schema } from '../types/schema-inspector';
|
||||
import { Table } from '../types/table';
|
||||
import { Column } from '../types/column';
|
||||
import extractMaxLength from '../utils/extract-max-length';
|
||||
|
||||
type RawColumn = {
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: 0 | 1;
|
||||
dflt_value: any;
|
||||
pk: 0 | 1;
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: 0 | 1;
|
||||
dflt_value: any;
|
||||
pk: 0 | 1;
|
||||
};
|
||||
|
||||
export default class SQLite implements Schema {
|
||||
knex: Knex;
|
||||
knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
// Tables
|
||||
// ===============================================================================================
|
||||
// Tables
|
||||
// ===============================================================================================
|
||||
|
||||
/**
|
||||
* List all existing tables in the current schema/database
|
||||
*/
|
||||
async tables(): Promise<string[]> {
|
||||
const records = await this.knex
|
||||
.select('name')
|
||||
.from('sqlite_master')
|
||||
.whereRaw(`type = 'table' AND name NOT LIKE 'sqlite_%'`);
|
||||
return records.map(({ name }) => name) as string[];
|
||||
}
|
||||
/**
|
||||
* List all existing tables in the current schema/database
|
||||
*/
|
||||
async tables(): Promise<string[]> {
|
||||
const records = await this.knex
|
||||
.select('name')
|
||||
.from('sqlite_master')
|
||||
.whereRaw(`type = 'table' AND name NOT LIKE 'sqlite_%'`);
|
||||
return records.map(({ name }) => name) as string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table info for a given table. If table parameter is undefined, it will return all tables
|
||||
* in the current schema/database
|
||||
*/
|
||||
tableInfo(): Promise<Table[]>;
|
||||
tableInfo(table: string): Promise<Table>;
|
||||
async tableInfo(table?: string) {
|
||||
const query = this.knex
|
||||
.select('name', 'sql')
|
||||
.from('sqlite_master')
|
||||
.where({ type: 'table' })
|
||||
.andWhereRaw(`name NOT LIKE 'sqlite_%'`);
|
||||
/**
|
||||
* Get the table info for a given table. If table parameter is undefined, it will return all tables
|
||||
* in the current schema/database
|
||||
*/
|
||||
tableInfo(): Promise<Table[]>;
|
||||
tableInfo(table: string): Promise<Table>;
|
||||
async tableInfo(table?: string) {
|
||||
const query = this.knex
|
||||
.select('name', 'sql')
|
||||
.from('sqlite_master')
|
||||
.where({ type: 'table' })
|
||||
.andWhereRaw(`name NOT LIKE 'sqlite_%'`);
|
||||
|
||||
if (table) {
|
||||
query.andWhere({ name: table });
|
||||
}
|
||||
if (table) {
|
||||
query.andWhere({ name: table });
|
||||
}
|
||||
|
||||
let records = await query;
|
||||
let records = await query;
|
||||
|
||||
records = records.map((table) => ({
|
||||
name: table.name,
|
||||
sql: table.sql,
|
||||
}));
|
||||
records = records.map((table) => ({
|
||||
name: table.name,
|
||||
sql: table.sql,
|
||||
}));
|
||||
|
||||
if (table) {
|
||||
return records[0];
|
||||
}
|
||||
if (table) {
|
||||
return records[0];
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists in the current schema/database
|
||||
*/
|
||||
async hasTable(table: string): Promise<boolean> {
|
||||
const results = await this.knex
|
||||
.select(1)
|
||||
.from('sqlite_master')
|
||||
.where({ type: 'table', name: table });
|
||||
return results.length > 0;
|
||||
}
|
||||
/**
|
||||
* Check if a table exists in the current schema/database
|
||||
*/
|
||||
async hasTable(table: string): Promise<boolean> {
|
||||
const results = await this.knex
|
||||
.select(1)
|
||||
.from('sqlite_master')
|
||||
.where({ type: 'table', name: table });
|
||||
return results.length > 0;
|
||||
}
|
||||
|
||||
// Columns
|
||||
// ===============================================================================================
|
||||
// Columns
|
||||
// ===============================================================================================
|
||||
|
||||
/**
|
||||
* Get all the available columns in the current schema/database. Can be filtered to a specific table
|
||||
*/
|
||||
async columns(table?: string): Promise<{ table: string; column: string }[]> {
|
||||
if (table) {
|
||||
const columns = await this.knex.raw<RawColumn[]>(
|
||||
`PRAGMA table_info(??)`,
|
||||
table
|
||||
);
|
||||
return columns.map((column) => ({ table, column: column.name }));
|
||||
}
|
||||
/**
|
||||
* Get all the available columns in the current schema/database. Can be filtered to a specific table
|
||||
*/
|
||||
async columns(table?: string): Promise<{ table: string; column: string }[]> {
|
||||
if (table) {
|
||||
const columns = await this.knex.raw<RawColumn[]>(`PRAGMA table_info(??)`, table);
|
||||
return columns.map((column) => ({ table, column: column.name }));
|
||||
}
|
||||
|
||||
const tables = await this.tables();
|
||||
const columnsPerTable = await Promise.all(
|
||||
tables.map(async (table) => await this.columns(table))
|
||||
);
|
||||
return flatten(columnsPerTable);
|
||||
}
|
||||
const tables = await this.tables();
|
||||
const columnsPerTable = await Promise.all(
|
||||
tables.map(async (table) => await this.columns(table))
|
||||
);
|
||||
return flatten(columnsPerTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column info for all columns, columns in a given table, or a specific column.
|
||||
*/
|
||||
columnInfo(): Promise<Column[]>;
|
||||
columnInfo(table: string): Promise<Column[]>;
|
||||
columnInfo(table: string, column: string): Promise<Column>;
|
||||
async columnInfo(table?: string, column?: string) {
|
||||
const getColumnsForTable = async (table: string): Promise<Column[]> => {
|
||||
const tablesWithAutoIncrementPrimaryKeys = (
|
||||
await this.knex
|
||||
.select('name')
|
||||
.from('sqlite_master')
|
||||
.whereRaw(`sql LIKE "%AUTOINCREMENT%"`)
|
||||
).map(({ name }) => name);
|
||||
/**
|
||||
* Get the column info for all columns, columns in a given table, or a specific column.
|
||||
*/
|
||||
columnInfo(): Promise<Column[]>;
|
||||
columnInfo(table: string): Promise<Column[]>;
|
||||
columnInfo(table: string, column: string): Promise<Column>;
|
||||
async columnInfo(table?: string, column?: string) {
|
||||
const getColumnsForTable = async (table: string): Promise<Column[]> => {
|
||||
const tablesWithAutoIncrementPrimaryKeys = (
|
||||
await this.knex.select('name').from('sqlite_master').whereRaw(`sql LIKE "%AUTOINCREMENT%"`)
|
||||
).map(({ name }) => name);
|
||||
|
||||
const columns: RawColumn[] = await this.knex.raw(
|
||||
`PRAGMA table_info(??)`,
|
||||
table
|
||||
);
|
||||
const columns: RawColumn[] = await this.knex.raw(`PRAGMA table_info(??)`, table);
|
||||
|
||||
const foreignKeys = await this.knex.raw<
|
||||
{ table: string; from: string; to: string }[]
|
||||
>(`PRAGMA foreign_key_list(??)`, table);
|
||||
const foreignKeys = await this.knex.raw<{ table: string; from: string; to: string }[]>(
|
||||
`PRAGMA foreign_key_list(??)`,
|
||||
table
|
||||
);
|
||||
|
||||
return columns.map(
|
||||
(raw): Column => {
|
||||
const foreignKey = foreignKeys.find((fk) => fk.from === raw.name);
|
||||
return columns.map(
|
||||
(raw): Column => {
|
||||
const foreignKey = foreignKeys.find((fk) => fk.from === raw.name);
|
||||
|
||||
return {
|
||||
name: raw.name,
|
||||
table: table,
|
||||
type: raw.type,
|
||||
default_value: raw.dflt_value,
|
||||
max_length: extractMaxLength(raw.dflt_value),
|
||||
/** @NOTE SQLite3 doesn't support precision/scale */
|
||||
precision: null,
|
||||
scale: null,
|
||||
is_nullable: raw.notnull === 0,
|
||||
is_primary_key: raw.pk === 1,
|
||||
has_auto_increment:
|
||||
raw.pk === 1 &&
|
||||
tablesWithAutoIncrementPrimaryKeys.includes(table),
|
||||
foreign_key_column: foreignKey?.to || null,
|
||||
foreign_key_table: foreignKey?.table || null,
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
return {
|
||||
name: raw.name,
|
||||
table: table,
|
||||
type: raw.type,
|
||||
default_value: raw.dflt_value,
|
||||
max_length: extractMaxLength(raw.dflt_value),
|
||||
/** @NOTE SQLite3 doesn't support precision/scale */
|
||||
precision: null,
|
||||
scale: null,
|
||||
is_nullable: raw.notnull === 0,
|
||||
is_primary_key: raw.pk === 1,
|
||||
has_auto_increment: raw.pk === 1 && tablesWithAutoIncrementPrimaryKeys.includes(table),
|
||||
foreign_key_column: foreignKey?.to || null,
|
||||
foreign_key_table: foreignKey?.table || null,
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (!table) {
|
||||
const tables = await this.tables();
|
||||
const columnsPerTable = await Promise.all(
|
||||
tables.map(async (table) => await getColumnsForTable(table))
|
||||
);
|
||||
return flatten(columnsPerTable);
|
||||
}
|
||||
if (!table) {
|
||||
const tables = await this.tables();
|
||||
const columnsPerTable = await Promise.all(
|
||||
tables.map(async (table) => await getColumnsForTable(table))
|
||||
);
|
||||
return flatten(columnsPerTable);
|
||||
}
|
||||
|
||||
if (table && !column) {
|
||||
return await getColumnsForTable(table);
|
||||
}
|
||||
if (table && !column) {
|
||||
return await getColumnsForTable(table);
|
||||
}
|
||||
|
||||
const columns = await getColumnsForTable(table);
|
||||
return columns.find((columnInfo) => columnInfo.name === column);
|
||||
}
|
||||
const columns = await getColumnsForTable(table);
|
||||
return columns.find((columnInfo) => columnInfo.name === column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists in the current schema/database
|
||||
*/
|
||||
async hasColumn(table: string, column: string): Promise<boolean> {
|
||||
let isColumn = false;
|
||||
const results = await this.knex.raw(
|
||||
`SELECT COUNT(*) AS ct FROM pragma_table_info('${table}') WHERE name='${column}'`
|
||||
);
|
||||
const resultsVal = results[0]['ct'];
|
||||
if (resultsVal !== 0) {
|
||||
isColumn = true;
|
||||
}
|
||||
return isColumn;
|
||||
}
|
||||
/**
|
||||
* Check if a table exists in the current schema/database
|
||||
*/
|
||||
async hasColumn(table: string, column: string): Promise<boolean> {
|
||||
let isColumn = false;
|
||||
const results = await this.knex.raw(
|
||||
`SELECT COUNT(*) AS ct FROM pragma_table_info('${table}') WHERE name='${column}'`
|
||||
);
|
||||
const resultsVal = results[0]['ct'];
|
||||
if (resultsVal !== 0) {
|
||||
isColumn = true;
|
||||
}
|
||||
return isColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary key column for the given table
|
||||
*/
|
||||
async primary(table: string): Promise<string> {
|
||||
const columns = await this.knex.raw<RawColumn[]>(
|
||||
`PRAGMA table_info(??)`,
|
||||
table
|
||||
);
|
||||
const pkColumn = columns.find((col) => col.pk !== 0);
|
||||
return pkColumn!.name;
|
||||
}
|
||||
/**
|
||||
* Get the primary key column for the given table
|
||||
*/
|
||||
async primary(table: string): Promise<string> {
|
||||
const columns = await this.knex.raw<RawColumn[]>(`PRAGMA table_info(??)`, table);
|
||||
const pkColumn = columns.find((col) => col.pk !== 0);
|
||||
return pkColumn!.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
@@ -7,7 +8,8 @@
|
||||
"outDir": "./dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user