Remove outdated tests, fix imports

This commit is contained in:
rijkvanzanten
2020-11-09 12:29:01 -05:00
parent 651eef4878
commit 528112314d
97 changed files with 181 additions and 50683 deletions

View File

@@ -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",

View File

@@ -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') });

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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/';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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',

View File

@@ -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';

View File

@@ -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) {

View File

@@ -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';
/**

View File

@@ -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,
},
},
],
};

View File

@@ -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(),
})),
});

View File

@@ -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
}
);
};

View File

@@ -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.

View File

@@ -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',
},
],
],
};

View File

@@ -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",

View File

@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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]);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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']);
});
});

View File

@@ -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]);
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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',
});
});
});

View File

@@ -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']);
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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>');
});
});

View File

@@ -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>');
});
});

View File

@@ -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']);
});
});

View File

@@ -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);
});
});

View File

@@ -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,
},
]);
});
});

View File

@@ -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,
},
],
]);
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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]);
});
});

View File

@@ -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,
});
});
});

View File

@@ -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']);
});
});

View File

@@ -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);
});
});
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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);
});
});

View File

@@ -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]);
}
});
});

View File

@@ -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/');
});
});

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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();
});
});

View File

@@ -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();
});
});

View File

@@ -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');
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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([]);
});
});

View File

@@ -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');
});
});

View File

@@ -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']);
});
});

View File

@@ -12,7 +12,7 @@
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env", "jest"],
"types": ["webpack-env"],
"paths": {
"@/*": ["src/*"]
},

41189
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"
},

View File

@@ -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"

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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;
}
}

View File

@@ -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"]
}