@pequity/squirrel 4.1.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/link.js +1 -5
- package/dist/cjs/p-drawer.js +2 -6
- package/dist/cjs/p-inline-date-picker.js +6 -4
- package/dist/cjs/p-loading.js +3 -3
- package/dist/es/link.js +1 -5
- package/dist/es/p-drawer.js +2 -6
- package/dist/es/p-inline-date-picker.js +6 -4
- package/dist/es/p-loading.js +4 -4
- package/dist/squirrel/components/p-action-bar/p-action-bar.vue.d.ts +0 -1
- package/dist/squirrel/components/p-card/p-card.vue.d.ts +14 -6
- package/dist/squirrel/components/p-checkbox/p-checkbox.vue.d.ts +12 -4
- package/dist/squirrel/components/p-drawer/p-drawer.vue.d.ts +1 -2
- package/dist/squirrel/components/p-dropdown-select/p-dropdown-select.vue.d.ts +119 -21
- package/dist/squirrel/components/p-info-icon/p-info-icon.vue.d.ts +9 -3
- package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +14 -1
- package/dist/squirrel/components/p-link/p-link.vue.d.ts +15 -9
- package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +17 -8
- package/dist/squirrel/components/p-pagination-info/p-pagination-info.vue.d.ts +9 -3
- package/dist/squirrel/components/p-select/p-select.vue.d.ts +13 -6
- package/dist/squirrel/components/p-select-btn/p-select-btn.vue.d.ts +21 -15
- package/dist/squirrel/components/p-select-list/p-select-list.vue.d.ts +118 -21
- package/dist/squirrel/components/p-table/p-table.vue.d.ts +23 -14
- package/dist/squirrel/components/p-table-td/p-table-td.vue.d.ts +19 -12
- package/dist/style.css +34 -34
- package/package.json +8 -8
- package/squirrel/components/p-drawer/p-drawer.vue +4 -7
- package/squirrel/components/p-inline-date-picker/p-inline-date-picker.spec.js +176 -0
- package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +7 -7
- package/squirrel/components/p-input-number/p-input-number.spec.js +0 -1
- package/squirrel/components/p-loading/p-loading.spec.js +36 -25
- package/squirrel/components/p-loading/p-loading.vue +22 -15
- package/squirrel/utils/link.spec.js +26 -12
- package/squirrel/utils/link.ts +1 -14
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import PInlineDatePicker from '@squirrel/components/p-inline-date-picker/p-inline-date-picker.vue';
|
|
2
|
+
import { createWrapperFor } from '@tests/jest.helpers';
|
|
3
|
+
|
|
4
|
+
const createWrapper = (props) => {
|
|
5
|
+
return createWrapperFor(PInlineDatePicker, {
|
|
6
|
+
props,
|
|
7
|
+
global: {
|
|
8
|
+
stubs: {
|
|
9
|
+
DatePicker: {
|
|
10
|
+
name: 'DatePicker',
|
|
11
|
+
template: '<div class="date-picker-stub"></div>',
|
|
12
|
+
props: ['modelValue', 'selectAttribute', 'minDate', 'maxDate', 'masks', 'timezone', 'style'],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('PInlineDatePicker.vue', () => {
|
|
20
|
+
it('renders a datepicker', () => {
|
|
21
|
+
const wrapper = createWrapper();
|
|
22
|
+
|
|
23
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
24
|
+
|
|
25
|
+
expect(datePicker.props()).toEqual({
|
|
26
|
+
modelValue: null,
|
|
27
|
+
selectAttribute: { highlight: { class: 'bg-primary', contentClass: 'text-white' } },
|
|
28
|
+
minDate: null,
|
|
29
|
+
maxDate: null,
|
|
30
|
+
masks: { input: 'DD-MMM-YYYY', data: 'YYYY-MM-DD' },
|
|
31
|
+
style: {},
|
|
32
|
+
timezone: '',
|
|
33
|
+
});
|
|
34
|
+
expect(wrapper.find('label').exists()).toBe(false);
|
|
35
|
+
expect(wrapper.find('div.text-xs.text-on-error.mt-1').isVisible()).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('passes all props to the datepicker', () => {
|
|
39
|
+
const wrapper = createWrapper({
|
|
40
|
+
modelValue: '2024-05-19',
|
|
41
|
+
minDate: new Date('2024-05-01'),
|
|
42
|
+
maxDate: new Date('2024-05-31'),
|
|
43
|
+
timezone: 'UTC',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
47
|
+
|
|
48
|
+
expect(datePicker.props().modelValue).toEqual(new Date('2024-05-19'));
|
|
49
|
+
expect(datePicker.props().minDate).toEqual(new Date('2024-05-01'));
|
|
50
|
+
expect(datePicker.props().maxDate).toEqual(new Date('2024-05-31'));
|
|
51
|
+
expect(datePicker.props().timezone).toBe('UTC');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders a label when the label prop is set', () => {
|
|
55
|
+
const wrapper = createWrapper({ label: 'test datepicker' });
|
|
56
|
+
|
|
57
|
+
expect(wrapper.find('label').text()).toBe('test datepicker');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('hides the component when the hidden prop is set', () => {
|
|
61
|
+
const wrapper = createWrapper({ hidden: true });
|
|
62
|
+
|
|
63
|
+
expect(wrapper.classes()).toContain('hidden');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('adds the required class when the required prop is set', () => {
|
|
67
|
+
const wrapper = createWrapper({ label: 'test datepicker', required: true });
|
|
68
|
+
|
|
69
|
+
expect(wrapper.find('label').classes()).toEqual([
|
|
70
|
+
'block',
|
|
71
|
+
'mb-1',
|
|
72
|
+
'font-bold',
|
|
73
|
+
'text-sm',
|
|
74
|
+
"after:content-['_*']",
|
|
75
|
+
'after:text-on-error',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
expect(wrapper.props().required).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('passes listeners to the datepicker', () => {
|
|
82
|
+
// Since DatePicker emits are not defined on PInlineDatePicker we need to Spy on console.warn and mock the implementation to suppress the warning:
|
|
83
|
+
// [Vue warn]: Component emitted event "update:view" but it is neither declared in the emits option nor as an "onUpdate:view" prop.
|
|
84
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
85
|
+
|
|
86
|
+
const testFn = jest.fn();
|
|
87
|
+
|
|
88
|
+
const wrapper = createWrapperFor({
|
|
89
|
+
template: `<PInlineDatePicker v-model="dateStrVal" @update:view="testEvent" />`,
|
|
90
|
+
components: { PInlineDatePicker },
|
|
91
|
+
data() {
|
|
92
|
+
return {
|
|
93
|
+
dateStrVal: '2024-09-01',
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
methods: {
|
|
97
|
+
testEvent() {
|
|
98
|
+
testFn();
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
104
|
+
|
|
105
|
+
datePicker.vm.$emit('update:view');
|
|
106
|
+
|
|
107
|
+
expect(testFn).toHaveBeenCalled();
|
|
108
|
+
|
|
109
|
+
warnSpy.mockRestore();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('sets the disabled state correctly', () => {
|
|
113
|
+
const wrapper = createWrapper({ label: 'test datepicker', disabled: true });
|
|
114
|
+
|
|
115
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
116
|
+
|
|
117
|
+
expect(['pointer-events-none', 'opacity-70'].every((c) => datePicker.classes().includes(c))).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it(`updates the value when a day is clicked`, async () => {
|
|
121
|
+
const wrapper = createWrapperFor({
|
|
122
|
+
template: `<PInlineDatePicker v-model="dateStrVal" />`,
|
|
123
|
+
components: { PInlineDatePicker },
|
|
124
|
+
data() {
|
|
125
|
+
return {
|
|
126
|
+
dateStrVal: '2024-09-01',
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
132
|
+
|
|
133
|
+
datePicker.vm.$emit('dayclick', { id: '2024-09-02' });
|
|
134
|
+
|
|
135
|
+
expect(wrapper.vm.dateStrVal).toBe('2024-09-02');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it(`updates the datepicker value when modelValue is changed`, async () => {
|
|
139
|
+
const wrapper = createWrapper({ modelValue: '2024-09-01' });
|
|
140
|
+
|
|
141
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
142
|
+
datePicker.vm.move = jest.fn();
|
|
143
|
+
|
|
144
|
+
expect(datePicker.props().modelValue).toEqual(new Date('2024-09-01'));
|
|
145
|
+
|
|
146
|
+
await wrapper.setProps({ modelValue: '2024-09-02' });
|
|
147
|
+
|
|
148
|
+
expect(datePicker.vm.move).toHaveBeenCalled();
|
|
149
|
+
expect(datePicker.props().modelValue).toEqual(new Date('2024-09-02'));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('sets the error state correctly', () => {
|
|
153
|
+
const wrapper = createWrapper({
|
|
154
|
+
label: 'test datepicker',
|
|
155
|
+
errorMsg: 'datepicker has error',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(wrapper.attributes()['data-has-error']).toBeDefined();
|
|
159
|
+
const datePicker = wrapper.findComponent({ name: 'DatePicker' });
|
|
160
|
+
|
|
161
|
+
expect(datePicker.props().style).toEqual({
|
|
162
|
+
border: '1px solid var(--color-on-error)',
|
|
163
|
+
});
|
|
164
|
+
const errorDiv = wrapper.find('div.text-xs.text-on-error.mt-1');
|
|
165
|
+
expect(errorDiv.isVisible()).toBe(true);
|
|
166
|
+
expect(errorDiv.text()).toBe('datepicker has error');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('emits null when date is invalid', async () => {
|
|
170
|
+
const wrapper = createWrapper({ label: 'test datepicker', modelValue: 'not a date' });
|
|
171
|
+
|
|
172
|
+
await wrapper.vm.$nextTick();
|
|
173
|
+
|
|
174
|
+
expect(wrapper.emitted()['update:modelValue'][0][0]).toBe(null);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
<script lang="ts">
|
|
26
26
|
import dayjs from 'dayjs';
|
|
27
27
|
import inputClassesMixin from '@squirrel/utils/inputClassesMixin';
|
|
28
|
+
import { type AttributeConfig } from 'v-calendar/dist/types/src/utils/attribute';
|
|
28
29
|
import { DatePicker } from 'v-calendar';
|
|
29
30
|
import { type PropType, type StyleValue, defineComponent } from 'vue';
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const selectAttribute = { highlight: { class: 'bg-primary', contentClass: 'text-white' } } as any;
|
|
32
|
+
const selectAttribute: AttributeConfig = {
|
|
33
|
+
highlight: { class: 'bg-primary', contentClass: 'text-white' },
|
|
34
|
+
};
|
|
35
35
|
|
|
36
36
|
const DEFAULT_MASKS = {
|
|
37
37
|
// The mask for the input
|
|
@@ -112,7 +112,7 @@ export default defineComponent({
|
|
|
112
112
|
},
|
|
113
113
|
watch: {
|
|
114
114
|
modelValue: {
|
|
115
|
-
handler(nV) {
|
|
115
|
+
async handler(nV) {
|
|
116
116
|
const date = dayjs(nV, this.masks.data).toDate();
|
|
117
117
|
|
|
118
118
|
if (nV && date.toString() === 'Invalid Date') {
|
|
@@ -123,8 +123,8 @@ export default defineComponent({
|
|
|
123
123
|
this.innerValue = nV ? dayjs(nV, this.masks.data).toDate() : null;
|
|
124
124
|
const datepicker = this.$refs.datepicker as { move: (val: unknown) => void };
|
|
125
125
|
|
|
126
|
-
if (datepicker && this.innerValue) {
|
|
127
|
-
datepicker.move(this.innerValue);
|
|
126
|
+
if (datepicker && typeof datepicker.move === 'function' && this.innerValue) {
|
|
127
|
+
await datepicker.move(this.innerValue);
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
immediate: true,
|
|
@@ -53,6 +53,7 @@ const createWrapper = (options) => {
|
|
|
53
53
|
{
|
|
54
54
|
global: {
|
|
55
55
|
stubs: {
|
|
56
|
+
Teleport: true,
|
|
56
57
|
PSkeletonLoader: true,
|
|
57
58
|
},
|
|
58
59
|
},
|
|
@@ -232,45 +233,55 @@ describe('PLoading.vue', () => {
|
|
|
232
233
|
template: `<div>{{ modelValue }}</div>`,
|
|
233
234
|
});
|
|
234
235
|
|
|
235
|
-
const wrapper = createWrapperFor(
|
|
236
|
-
|
|
236
|
+
const wrapper = createWrapperFor(
|
|
237
|
+
{
|
|
238
|
+
template: `
|
|
237
239
|
<button class="request-1-sec-component-props" @click="fireRequestComponentProps"></button>
|
|
238
240
|
<button class="update-props" @click="updateProps"></button>
|
|
239
241
|
`,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
242
|
+
components: { TestComponent },
|
|
243
|
+
setup() {
|
|
244
|
+
const componentProps = ref({ modelValue: 0 });
|
|
245
|
+
const { loadingShow, loadingHide } = usePLoading();
|
|
246
|
+
|
|
247
|
+
const fireRequestComponentProps = async () => {
|
|
248
|
+
const id = `component-props`;
|
|
249
|
+
componentProps.value.modelValue = 10;
|
|
250
|
+
loadingShow({ id, content: TestComponent, props: componentProps });
|
|
251
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
252
|
+
loadingHide(id);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const updateProps = () => {
|
|
256
|
+
componentProps.value.modelValue = 20;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return { loadingShow, loadingHide, fireRequestComponentProps, updateProps, componentProps };
|
|
260
|
+
},
|
|
258
261
|
},
|
|
259
|
-
|
|
262
|
+
{
|
|
263
|
+
global: {
|
|
264
|
+
stubs: {
|
|
265
|
+
Teleport: true,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
);
|
|
260
270
|
|
|
261
271
|
await wrapper.find('.request-1-sec-component-props').trigger('click');
|
|
262
272
|
|
|
263
273
|
jest.advanceTimersByTime(300);
|
|
264
274
|
await waitNT(appWrapper.vm);
|
|
265
|
-
const
|
|
275
|
+
const getTestComponent = () => appWrapper.findComponent(TestComponent);
|
|
276
|
+
|
|
266
277
|
expect(appWrapper.find('[aria-busy]').exists()).toBe(true);
|
|
267
|
-
expect(
|
|
268
|
-
expect(
|
|
278
|
+
expect(getTestComponent().exists()).toBe(true);
|
|
279
|
+
expect(getTestComponent().props().modelValue).toBe(10);
|
|
269
280
|
|
|
270
281
|
jest.advanceTimersByTime(300);
|
|
271
282
|
await waitNT(appWrapper.vm);
|
|
272
283
|
await wrapper.find('.update-props').trigger('click');
|
|
273
|
-
expect(
|
|
284
|
+
expect(getTestComponent().props().modelValue).toBe(20);
|
|
274
285
|
|
|
275
286
|
jest.advanceTimersByTime(500);
|
|
276
287
|
await waitNT(appWrapper.vm);
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<Transition name="pm-backdrop-transition" enter-active-class="fadeInDown" leave-active-class="fadeOutUp">
|
|
4
4
|
<div
|
|
5
|
-
|
|
6
|
-
class="
|
|
5
|
+
v-if="show"
|
|
6
|
+
class="fixed left-0 top-0 z-[120] flex w-full justify-center"
|
|
7
|
+
aria-live="polite"
|
|
8
|
+
aria-busy="true"
|
|
7
9
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
<div
|
|
11
|
+
:style="{ width: `${width}px`, height: `${height}px` }"
|
|
12
|
+
class="overflow-hidden rounded-b border-x border-b border-p-gray-30 bg-p-blue-10 shadow-sm transition-all duration-300"
|
|
13
|
+
>
|
|
14
|
+
<Transition enter-from-class="opacity-0" enter-active-class="transition duration-500">
|
|
15
|
+
<Component :is="content" v-if="isComponent(content)" v-bind="componentProps" />
|
|
16
|
+
<div v-else :class="textDivClass">{{ content }}</div>
|
|
17
|
+
</Transition>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</Transition>
|
|
21
|
+
<div v-if="content" class="invisible fixed">
|
|
22
|
+
<div ref="dimsReference">
|
|
23
|
+
<Component :is="content" v-if="isComponent(content)" v-bind="componentProps" />
|
|
24
|
+
<div v-else :class="textDivClass">{{ content }}</div>
|
|
12
25
|
</div>
|
|
13
26
|
</div>
|
|
14
|
-
</
|
|
15
|
-
<div v-if="content" class="invisible fixed">
|
|
16
|
-
<div ref="dimsReference">
|
|
17
|
-
<Component :is="content" v-if="isComponent(content)" v-bind="componentProps" />
|
|
18
|
-
<div v-else :class="textDivClass">{{ content }}</div>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
27
|
+
</Teleport>
|
|
21
28
|
</template>
|
|
22
29
|
|
|
23
30
|
<script setup lang="ts">
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { isExternalLink } from '@squirrel/utils/link';
|
|
2
2
|
|
|
3
3
|
describe('isExternalLink', () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
delete window.location;
|
|
6
|
+
|
|
7
|
+
window.location = new URL('https://www.example.com');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it.each([
|
|
11
|
+
'https://www.example.com',
|
|
12
|
+
'http://www.example.com',
|
|
13
|
+
'//www.example.com',
|
|
14
|
+
'https://www.pequity.com',
|
|
15
|
+
'http://www.pequity.com',
|
|
16
|
+
'//www.pequity.com',
|
|
17
|
+
])('should return true for external links (%s)', (val) => {
|
|
18
|
+
expect(isExternalLink(val)).toBe(true);
|
|
19
|
+
});
|
|
10
20
|
|
|
11
21
|
it.each(['/home', 'home', '#home', '/home//1/', '/home:1/', '#home:', { name: 'home' }])(
|
|
12
22
|
'should return false for internal links (%s)',
|
|
@@ -15,10 +25,14 @@ describe('isExternalLink', () => {
|
|
|
15
25
|
}
|
|
16
26
|
);
|
|
17
27
|
|
|
18
|
-
it.each([
|
|
19
|
-
'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
it.each([
|
|
29
|
+
'ftp://www.example.com',
|
|
30
|
+
'mailto:test@example.com',
|
|
31
|
+
'ftp://www.pequity.com',
|
|
32
|
+
'mailto:test@pequity.com',
|
|
33
|
+
'tel:+1234567890',
|
|
34
|
+
'sms:+1234567890',
|
|
35
|
+
])('should handle different protocols (%s)', (val) => {
|
|
36
|
+
expect(isExternalLink(val)).toBe(true);
|
|
37
|
+
});
|
|
24
38
|
});
|
package/squirrel/utils/link.ts
CHANGED
|
@@ -16,21 +16,8 @@ const isValidUrl = (url: string) => {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const checkDomain = function (url: string) {
|
|
20
|
-
url = normalizeUrl(url);
|
|
21
|
-
|
|
22
|
-
return url
|
|
23
|
-
.toLowerCase()
|
|
24
|
-
.replace(/([a-z])?:\/\//, '$1')
|
|
25
|
-
.split('/')[0];
|
|
26
|
-
};
|
|
27
|
-
|
|
28
19
|
export const isExternalLink = function (url: string) {
|
|
29
20
|
url = String(url);
|
|
30
21
|
|
|
31
|
-
return (
|
|
32
|
-
isValidUrl(url) &&
|
|
33
|
-
(url.indexOf(':') > -1 || url.indexOf('//') > -1) &&
|
|
34
|
-
checkDomain(location.href) !== checkDomain(url)
|
|
35
|
-
);
|
|
22
|
+
return isValidUrl(url) && (url.indexOf(':') > -1 || url.indexOf('//') > -1);
|
|
36
23
|
};
|