Skip to content

Commit 2f8a214

Browse files
committed
feat: translate into typescript
- use Composition API - split CronCore into useCronSegment, useCron and useCronComponent - replace RenderlessSelect with useSelect and useSelectComponent - removed default exports BREAKING CHANGE: restructured exports
1 parent d4791d6 commit 2f8a214

27 files changed

+1895
-262
lines changed

core-ts/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
"format": "prettier --write src/"
1414
},
1515
"dependencies": {
16+
"mustache": "^4.2.0",
1617
"vue": "^3.3.4"
1718
},
1819
"devDependencies": {
1920
"@rushstack/eslint-patch": "^1.3.3",
2021
"@tsconfig/node18": "^18.2.2",
2122
"@types/jsdom": "^21.1.3",
23+
"@types/mustache": "^4.2.3",
2224
"@types/node": "^18.17.17",
2325
"@vitejs/plugin-vue": "^4.3.4",
2426
"@vue/eslint-config-prettier": "^8.0.0",

core-ts/src/App.vue

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,80 @@
1-
<script setup lang="ts">
2-
import HelloWorld from './components/HelloWorld.vue'
3-
import TheWelcome from './components/TheWelcome.vue'
4-
</script>
5-
61
<template>
7-
<header>
8-
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
2+
<div id="app">
3+
Cron:
4+
<input :value="value" @change="value = ($event.target as HTMLInputElement).value" type="text" />
5+
<CronCore v-model="value" #default="{ period, error, fields }">
6+
<div>
7+
<span>
8+
{{ period.prefix }}:
9+
<select
10+
@input="
11+
period.events['update:model-value'](
12+
JSON.parse(($event.target as HTMLInputElement).value).id
13+
)
14+
"
15+
>
16+
<option v-for="item in period.items" :key="item.text" :value="JSON.stringify(item)">
17+
{{ item.text }}
18+
</option>
19+
</select>
20+
</span>
21+
22+
<template v-for="f in fields" :key="f.id">
23+
<span>
24+
{{ f.prefix }}
25+
<select @input="f.events['update:model-value'](getSelected($event.target))" multiple>
26+
<option v-for="item in f.items" :key="item.value" :value="item.value">
27+
{{ item.text }}
28+
</option>
29+
</select>
30+
{{ f.suffix }}
31+
</span>
32+
</template>
933

10-
<div class="wrapper">
11-
<HelloWorld msg="You did it!" />
12-
</div>
13-
</header>
34+
<div>-</div>
1435

15-
<main>
16-
<TheWelcome />
17-
</main>
36+
<div>error:{{ error }}</div>
37+
<div>period:{{ period.attrs.value }}</div>
38+
<div v-for="f in fields" :key="'div' + f.id">
39+
{{ f.id }}: {{ f.attrs.value }}, {{ f.cron }}, {{ f.selectedStr }}
40+
</div>
41+
</div>
42+
</CronCore>
43+
</div>
1844
</template>
1945

2046
<style scoped>
21-
header {
22-
line-height: 1.5;
47+
select[multiple] {
48+
height: 200px;
2349
}
50+
</style>
2451

25-
.logo {
26-
display: block;
27-
margin: 0 auto 2rem;
28-
}
52+
<script lang="ts">
53+
import { useCronComponent } from './components/cron-core'
2954
30-
@media (min-width: 1024px) {
31-
header {
32-
display: flex;
33-
place-items: center;
34-
padding-right: calc(var(--section-gap) / 2);
35-
}
55+
export default {
56+
components: {
57+
'CronCore': useCronComponent()
58+
},
59+
props: {},
60+
data() {
61+
return {
62+
value: '* * * * *'
63+
}
64+
},
3665
37-
.logo {
38-
margin: 0 2rem 0 0;
39-
}
66+
computed: {},
67+
68+
watch: {},
4069
41-
header .wrapper {
42-
display: flex;
43-
place-items: flex-start;
44-
flex-wrap: wrap;
70+
methods: {
71+
getSelected(select: any) {
72+
const options = select && select.options
73+
return Array.from(options)
74+
.filter((opt: any) => opt.selected)
75+
.map((opt: any) => opt.value)
76+
}
4577
}
4678
}
47-
</style>
79+
</script>
80+
./components/core

core-ts/src/__tests__/cron.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { arrayToSegment, cronToSegment } from '@/cron'
2+
import { FieldWrapper } from '@/types'
3+
import { genItems } from '@/util'
4+
import { describe, expect, it } from 'vitest'
5+
6+
const r = (min: number, max: number) => {
7+
return new FieldWrapper({ id: 'fieldId', items: genItems(min, max) })
8+
}
9+
10+
describe('segments', () => {
11+
it('cronToSegment', () => {
12+
const cronToArray = (cron: string, field: FieldWrapper) => {
13+
return cronToSegment(cron, field)?.toArray() ?? null
14+
}
15+
16+
expect(cronToArray('*', r(1, 3))).toEqual([])
17+
expect(cronToArray('1,3,5', r(0, 24))).toEqual([1, 3, 5])
18+
expect(cronToArray('*/5', r(0, 11))).toEqual([0, 5, 10])
19+
expect(cronToArray('*/5', r(1, 11))).toEqual([5, 10])
20+
expect(cronToArray('10-15', r(0, 59))).toEqual([10, 11, 12, 13, 14, 15])
21+
expect(cronToArray('10-11,20-22,30-33', r(0, 59))).toEqual([10, 11, 20, 21, 22, 30, 31, 32, 33])
22+
expect(cronToArray('5,7-8', r(0, 59))).toEqual([5, 7, 8])
23+
24+
expect(cronToArray('x', r(0, 59))).toBe(null)
25+
expect(cronToArray('1-60', r(0, 59))).toBe(null)
26+
expect(cronToArray('0-10', r(1, 59))).toBe(null)
27+
expect(cronToArray('60', r(0, 59))).toBe(null)
28+
expect(cronToArray('0', r(1, 10))).toBe(null)
29+
expect(cronToArray('*/90', r(1, 10))).toBe(null)
30+
})
31+
32+
it('arrayToSegment', () => {
33+
const arrayToCron = (arr: number[], field: FieldWrapper) => {
34+
return arrayToSegment(arr, field)?.toCron() ?? null
35+
}
36+
37+
expect(arrayToCron([1, 10], r(1, 10))).toEqual('1,10')
38+
expect(arrayToCron([1, 2, 3], r(1, 10))).toEqual('1-3')
39+
expect(arrayToCron([2, 4, 6], r(1, 10))).toEqual('2,4,6')
40+
expect(arrayToCron([], r(1, 3))).toEqual('*')
41+
expect(arrayToCron([1, 2, 3], r(1, 3))).toEqual('*')
42+
expect(arrayToCron([0, 5, 10], r(0, 10))).toEqual('*/5')
43+
expect(arrayToCron([7, 14, 21, 28], r(5, 30))).toEqual('*/7')
44+
expect(arrayToCron([0, 5, 10], r(0, 20))).toEqual('0,5,10')
45+
expect(arrayToCron([1, 2, 5, 8, 9, 10], r(1, 10))).toEqual('1-2,5,8-10')
46+
})
47+
})

core-ts/src/__tests__/locale.spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { getLocale } from '@/locale'
2+
import { CronType, TextPosition } from '@/types'
3+
import { describe, expect, it } from 'vitest'
4+
5+
describe('locale', () => {
6+
it('getLocale', () => {
7+
const testCases = [
8+
{ locale: 'en', expected: 'Hour' },
9+
{ locale: 'foo-bar', expected: 'Hour' },
10+
{ locale: 'de', expected: 'Stunde' },
11+
{ locale: 'DE-AT', expected: 'Stunde' },
12+
{ locale: 'de-li', expected: 'Stunde' }
13+
]
14+
15+
for (const test of testCases) {
16+
const l = getLocale(test.locale)
17+
expect(l.getLocaleStr('hour', 'text')).toBe(test.expected)
18+
}
19+
})
20+
21+
it('getLocaleStr', () => {
22+
const l = getLocale('en', {
23+
custom: {
24+
'*': 'bar',
25+
message: 'baz'
26+
}
27+
})
28+
29+
expect(l.getLocaleStr('year', 'minute', 'empty', 'text')).toBe('every {{field.id}}')
30+
expect(l.getLocaleStr('year', 'dayOfWeek', 'value', 'prefix')).toBe('and')
31+
expect(l.getLocaleStr('year', 'minute', 'range', 'prefix')).toBe(':')
32+
expect(l.getLocaleStr('custom', 'foo')).toBe('bar')
33+
expect(l.getLocaleStr('custom', 'message')).toBe('baz')
34+
})
35+
36+
it('getLocaleStr pt', () => {
37+
const l = getLocale('pt', {
38+
custom: {
39+
'*': 'bar',
40+
message: 'baz'
41+
}
42+
})
43+
44+
expect(l.getLocaleStr('year', 'minute', 'empty', 'text')).toBe('cada minuto')
45+
expect(l.getLocaleStr('year', 'dayOfWeek', 'value', 'prefix')).toBe('e de')
46+
expect(l.getLocaleStr('year', 'minute', 'range', 'prefix')).toBe(':')
47+
expect(l.getLocaleStr('custom', 'foo')).toBe('bar')
48+
expect(l.getLocaleStr('custom', 'message')).toBe('baz')
49+
})
50+
51+
it('render', () => {
52+
const l = getLocale('en', {
53+
'*': {
54+
'*': {
55+
value: {
56+
text: '{{start.text}}-{{end.text}}'
57+
}
58+
}
59+
}
60+
})
61+
62+
expect(
63+
l.render('period', 'field', CronType.Value, TextPosition.Text, {
64+
start: {
65+
text: 'foo'
66+
},
67+
end: {
68+
text: 'bar'
69+
}
70+
})
71+
).toBe('foo-bar')
72+
})
73+
})

core-ts/src/__tests__/util.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Range, deepMerge } from '@/util'
2+
import { describe, expect, it } from 'vitest'
3+
4+
describe('util', () => {
5+
it('Range', () => {
6+
const r = new Range(0, 10, 2)
7+
expect(r[0]).toBe(0)
8+
expect(r[5]).toBe(10)
9+
expect(Array.from(r)).toEqual([0, 2, 4, 6, 8, 10])
10+
})
11+
12+
it('deepMerge', () => {
13+
expect(deepMerge({ a: { a: 1 } }, { a: { b: 1 }, b: 1 })).toEqual({ a: { a: 1, b: 1 }, b: 1 })
14+
expect(deepMerge({}, { a: { b: 1 } })).toEqual({ a: { b: 1 } })
15+
expect(deepMerge({ a: { b: 1 } }, { a: { b: 2 } })).toEqual({ a: { b: 2 } })
16+
})
17+
})

core-ts/src/components/HelloWorld.vue

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)