Skip to content

Instantly share code, notes, and snippets.

@lmiller1990
Created October 17, 2020 02:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lmiller1990/bca97f1a32e5878ea1652a4b594d3ab1 to your computer and use it in GitHub Desktop.
Save lmiller1990/bca97f1a32e5878ea1652a4b594d3ab1 to your computer and use it in GitHub Desktop.
<template>
<h3>Luxon</h3>
<date-time
v-model="dateLuxon"
:deserialize="deserialize"
:serialize="serialize"
/>
{{ dateLuxon }}
<h3>Moment</h3>
<date-time
v-model="dateMoment"
:deserialize="deserializeMoment"
:serialize="serializeMoment"
/>
{{ dateMoment.format ? dateMoment.format('YYYY-MM-DD') : dateMoment }}
<hr />
</template>
<script>
import { ref } from 'vue'
import { DateTime } from 'luxon'
import {
serialize,
deserialize,
serializeMoment,
deserializeMoment
} from './date-time-serializers.js'
import moment from 'moment'
import dateTime from './date-time.vue'
export default {
components: { dateTime },
setup() {
const dateLuxon = ref(DateTime.fromObject({
year: '2019',
month: '01',
day: '01',
}))
const dateMoment = ref(moment('2019-02-02', 'YYYY-MM-DD'))
return {
dateLuxon,
serialize,
deserialize,
dateMoment,
serializeMoment,
deserializeMoment
}
}
}
</script>
import { DateTime } from 'luxon'
import moment from 'moment'
export function serializeMoment(value) {
const toString = `${value.year}-${value.month.padStart(2, '0')}-${value.day.padStart(2, '0')}`
const toObject = moment(toString, 'YYYY-MM-DD', true)
if (toObject.isValid()) {
return toObject
}
return
}
export function deserializeMoment(value) {
if (!moment.isMoment(value)) {
return value
}
return {
year: value.year().toString(),
month: (value.month() + 1).toString(),
day: value.date().toString()
}
}
export function deserialize(value) {
return {
year: value.get('year'),
month: value.get('month'),
day: value.get('day')
}
}
export function serialize(value) {
try {
const obj = DateTime.fromObject(value)
if (obj.invalid) {
return
}
} catch {
return
}
return DateTime.fromObject(value)
}
import { mount } from '@vue/test-utils'
import moment from 'moment'
import { DateTime } from 'luxon'
import dateTime from './date-time.vue'
import {
serialize,
deserialize,
serializeMoment,
deserializeMoment
} from './date-time-serializers.js'
describe('serializeMoment', () => {
it('serializes valid moment', () => {
const actual = serializeMoment({ year: '2020', month: '1', day: '1' })
// compare as strings. moment is pain.
expect(actual.toString()).toEqual(moment('2020-01-01').toString())
})
it('returns undefined for invalid moment', () => {
const actual = serializeMoment({ year: '200000020', month: '1xxxxx', day: 'bbbb' })
expect(actual).toEqual(undefined)
})
})
describe('deserialize', () => {
it('deserializes to Luxon DateTime', () => {
const actual = deserialize(DateTime.fromObject({ year: '2020', month: '1', day: '1' }))
expect(actual).toEqual({ year: 2020, month: 1, day: 1 })
})
})
describe('serialize', () => {
it('serializes valid Luxon DateTime', () => {
const actual = serialize({ year: '2020', month: '1', day: '1' })
expect(actual).toEqual(DateTime.fromObject({ year: 2020, month: 1, day: 1 }))
})
it('returns undefined for invalid Luxon DateTime', () => {
const actual = serialize({ year: '200000020', month: '1xxxxx', day: '1' })
expect(actual).toEqual(undefined)
})
})
describe('deserialize', () => {
it('deserializes to Luxon DateTime', () => {
const actual = deserialize(DateTime.fromObject({ year: '2020', month: '1', day: '1' }))
expect(actual).toEqual({ year: 2020, month: 1, day: 1 })
})
})
test('DateTime', async () => {
const wrapper = mount(dateTime, {
props: {
modelValue: DateTime.fromObject({ year: '2020', month: '1', day: '1' }),
serialize,
deserialize
}
})
await wrapper.find('[data-test-year]').setValue('2019')
await wrapper.find('[data-test-month]').setValue('2')
await wrapper.find('[data-test-day]').setValue('3')
// 3 successful updates, 3 emits.
expect(wrapper.emitted('update:modelValue')).toHaveLength(3)
// update:modelValue will not update the modelValue prop
// in Vue Test Utils, though.
// we could wrap this in another component and do something
// fancy but it's not really worth it. I think this is fine,
// since we know the limitations and understand why we are doing
// what we are doing here.
expect(wrapper.emitted('update:modelValue')[0][0]).toEqual(
DateTime.fromObject({ year: '2019', month: '1', day: '1' })
)
expect(wrapper.emitted('update:modelValue')[1][0]).toEqual(
DateTime.fromObject({ year: '2020', month: '2', day: '1' })
)
expect(wrapper.emitted('update:modelValue')[2][0]).toEqual(
DateTime.fromObject({ year: '2020', month: '1', day: '3' })
)
})
<template>
<input data-test-year :value="date.year" @input="update($event, 'year')" />
<input data-test-month :value="date.month" @input="update($event, 'month')" />
<input data-test-day :value="date.day" @input="update($event, 'day')" />
<pre>
date is:
{{ date }}
</pre>
</template>
<script>
import { reactive, watch, computed } from 'vue'
import { DateTime } from 'luxon'
export default {
props: {
modelValue: {
type: Object
},
serialize: {
type: Function
},
deserialize: {
type: Function
}
},
setup(props, { emit }) {
const date = computed(() => {
return props.deserialize(props.modelValue)
})
const update = ($event, field) => {
const { year, month, day } = props.deserialize(props.modelValue)
let newValue
if (field === 'year') {
newValue = { year: $event.target.value, month, day }
}
if (field === 'month') {
newValue = { year, month: $event.target.value, day }
}
if (field === 'day') {
newValue = { year, month, day: $event.target.value }
}
const asObject = props.serialize(newValue)
if (!asObject) {
return
}
emit('update:modelValue', asObject)
}
return {
update,
date
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment