Skip to content

Commit

Permalink
Finished core coverage and replaced or remove unnecessary utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
6XGate committed Nov 10, 2024
1 parent 3087a17 commit 5bae111
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 116 deletions.
81 changes: 11 additions & 70 deletions src/core/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import type { PortInfo } from '../main/services/ports'

export type LocationType = 'port' | 'ip'

/** Determines if a value is a serial port or simplify has the `ip:` prefix. */
export function isIpOrValidPort(value: string, ports: readonly PortInfo[]) {
switch (true) {
case value.startsWith('port:'):
return ports.find((port) => port.path === value.substring(5)) != null
case value.startsWith('ip:') && value[3] !== '/' && !value.slice(3).startsWith('file:'):
case value.startsWith('ip:') && value[3] !== '/' && !value.slice(3).startsWith('file:') && value.length > 3:
return true
default:
return false
}
}

/** Determines if a value is a serial port or IP address format. */
export function isValidLocation(value: string, ports: readonly PortInfo[]) {
switch (true) {
case value.startsWith('port:'):
Expand All @@ -26,21 +28,6 @@ export function isValidLocation(value: string, ports: readonly PortInfo[]) {
}
}

export function splitPath(value: string | undefined) {
if (value == null) {
return null
}

switch (true) {
case value.startsWith('port:'):
return ['port' as const, value.substring(5)] satisfies Fixed
case value.startsWith('ip:'):
return ['ip' as const, value.substring(3)] satisfies Fixed
default:
return null
}
}

// #region Host and Port

const hostWithOptionalPortPattern = /^((?:\[[A-Fa-f0-9.:]+\])|(?:[\p{N}\p{L}.-]+))(?::([1-9][0-9]*))?$/u
Expand All @@ -62,40 +49,6 @@ function zodParseHostWithOptionalPort(value: string) {
return [host, port] satisfies Fixed
}

export const hostWithOptionalPortSchema = z.string().transform(function splitHostAndPort(value, ctx) {
const result = parseHostWithOptionalPort(value)
if (result == null) {
ctx.addIssue({
message: 'Not a valid host or host:port combination',
code: 'invalid_string',
validation: 'regex',
fatal: true
})

return z.NEVER
}

return result
})

export function parseHostWithOptionalPort(value: string) {
const result = zodParseHostWithOptionalPort(value)
if (result == null) {
return undefined
}

const [host, port] = result
if (!host.success) {
return undefined
}

if (!port.success || port.data == null) {
return [host.data] satisfies Fixed
}

return [host.data, port.data] satisfies Fixed
}

export function isHostWithOptionalPort(value: string) {
const result = zodParseHostWithOptionalPort(value)
if (result == null) return false
Expand Down Expand Up @@ -232,23 +185,24 @@ function parsePossibleIpString(value: string) {
}

function isValidFullIpV6(value: string[]) {
const last = value.pop()
/* v8 ignore next 1 */ // Unlikely to be undefined.
const last = value.pop() ?? ''

// IPv4 translation.
if (ipV4Pattern.test(last ?? '')) {
if (ipV4Pattern.test(last)) {
return value.length === 6 && value.every((p) => ipPairPattern.test(p))
}

// IPv6, only 7 since we pop'ped the last.
return value.length === 7 && value.every((p) => ipPairPattern.test(p)) && ipPairPattern.test(last ?? '')
return value.length === 7 && value.every((p) => ipPairPattern.test(p)) && ipPairPattern.test(last)
}

function isValidCompactIpV6([left, right]: [string[], string[]]) {
// Undefind if right is empty.
const last = right.pop()
/* v8 ignore next 1 */ // Unlikely to be undefined.
const last = right.pop() ?? ''

// IPv4 translation, won't test on an empty right.
if (ipV4Pattern.test(last ?? '')) {
if (ipV4Pattern.test(last)) {
return (
left.length + right.length <= 6 &&
left.every((p) => ipPairPattern.test(p)) &&
Expand All @@ -263,7 +217,7 @@ function isValidCompactIpV6([left, right]: [string[], string[]]) {
left.length + right.length <= 7 &&
left.every((p) => ipPairPattern.test(p)) &&
right.every((p) => ipPairPattern.test(p)) &&
ipPairPattern.test(last ?? '')
ipPairPattern.test(last)
)
}

Expand All @@ -286,16 +240,3 @@ export const ipV6AddressSchema = z.string().refine(isIpV6Address)
// #endregion

// #endregion

// #region Schemas

// More Zod
export const schemas = {
hostname: hostNameSchema,
ipV4Address: ipV4AddressSchema,
ipV6Address: ipV6AddressSchema,
host: hostSchema,
hostWithOptionalPort: hostWithOptionalPortSchema
}

// #endregion
38 changes: 0 additions & 38 deletions src/core/object.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/renderer/modals/SwitchDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { useDialogs, useSwitchDialog } from './dialogs'
import type { I18nSchema } from '../locales/locales'
import type { NewSwitch } from '../services/switches'
import type { DeepReadonly } from 'vue'
import { deepClone } from '@/object'
const props = defineProps<{
// Dialog
Expand Down Expand Up @@ -40,7 +39,7 @@ const ports = usePorts()
onBeforeMount(ports.all)
// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- Prop reactivity not desired.
const target = ref<NewSwitch>(deepClone(props.switch))
const target = ref<NewSwitch>(structuredClone(props.switch))
const location = computed({
get: () => v$.path.$model,
set: (v) => {
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/modals/TieDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type { I18nSchema } from '../locales/locales'
import type { Source } from '../services/sources'
import type { NewTie } from '../services/ties'
import type { DeepReadonly } from 'vue'
import { deepClone } from '@/object'
const props = defineProps<{
// Dialog
Expand Down Expand Up @@ -46,7 +45,7 @@ const sources = useSources()
// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- Prop reactivity not desired.
const target = ref<NewTie>({
// eslint-disable-next-line vue/no-setup-props-reactivity-loss -- Prop reactivity not desired.
...deepClone(props.tie),
...structuredClone(props.tie),
...(props.source != null ? { sourceId: props.source._id } : {})
})
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/plugins/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import enDateTimes from '../locales/en/datetimes'
import enFunction from '../locales/en/functions'
import en from '../locales/en/messages.json'
import enNumbers from '../locales/en/numbers'
import { mergeLocaleParts } from '../support/locale'
import type { DateTimeSchema, Locales, MessageSchema, NumberSchema } from '../locales/locales'
import type { MergeDeep } from 'type-fest'
import { deepAssign } from '@/object'

type CompleteSchema = MergeDeep<MessageSchema, { $vuetify: typeof vuetifyEn$ }>
interface CompleteI18nSchema {
Expand Down Expand Up @@ -41,7 +41,7 @@ const i18n = createI18n<CompleteI18nSchema, Locales, false>({
},
// Localization messages and data.
messages: {
en: { ...deepAssign(en, enFunction), $vuetify: vuetifyEn$ }
en: { ...mergeLocaleParts(en, enFunction), $vuetify: vuetifyEn$ }
},
numberFormats: {
en: enNumbers
Expand Down
16 changes: 16 additions & 0 deletions src/renderer/support/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import is from '@sindresorhus/is'
import type { MergeDeep, UnknownRecord } from 'type-fest'

export function mergeLocaleParts<First extends UnknownRecord, Next extends UnknownRecord>(first: First, next: Next) {
const output: UnknownRecord = { ...first, ...next }
for (const [key, value] of Object.entries(first)) {
const now = output[key]
if (is.object(value) && is.object(now)) {
output[key] = mergeLocaleParts(value as UnknownRecord, now as UnknownRecord)
} else if (is.array(value) && is.array(now)) {
output[key] = now
}
}

return output as MergeDeep<First, Next>
}
2 changes: 0 additions & 2 deletions src/tests/core/error-handling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ describe('getZodMessage', () => {
assert(!badResult.success, 'Expected `!badResult.success`, got `badResult.success`')
const zodError = badResult.error

console.log(getZodMessage(zodError))
expect(getZodMessage(zodError)).toBe(zodError.errors[0]?.message)
})

Expand All @@ -27,7 +26,6 @@ describe('getZodMessage', () => {
assert(!badResult.success, 'Expected `!badResult.success`, got `badResult.success`')
const zodError = badResult.error

console.log(getZodMessage(zodError))
expect(getZodMessage(zodError)).toBe(`type: ${zodError.errors[0]?.message}`)
})
})
Expand Down
77 changes: 77 additions & 0 deletions src/tests/core/location.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
import type { PortEntry } from '../../main/services/ports'
import { isIpOrValidPort, isValidLocation } from '@/location'

let ports: PortEntry[]
beforeAll(() => {
ports = [
{
locationId: undefined,
manufacturer: 'Mock Serial Port',
path: '/dev/ttyS0',
pnpId: undefined,
productId: '8087',
serialNumber: '1',
title: 'Mock Serial Port',
vendorId: '8086'
}
]
})

afterAll(() => {
vi.restoreAllMocks()
vi.resetModules()
})

describe('isIpOrValidPort', () => {
test('port', () => {
expect(isIpOrValidPort('port:/dev/ttyS0', ports)).toBeTruthy()
expect(isIpOrValidPort('port:/dev/ttyS5', ports)).toBeFalsy()
expect(isIpOrValidPort('port:', ports)).toBeFalsy()
expect(isIpOrValidPort('port', ports)).toBeFalsy()
})
test('ip', () => {
expect(isIpOrValidPort('ip:example.com', ports)).toBeTruthy()
expect(isIpOrValidPort('ip:example', ports)).toBeTruthy()
expect(isIpOrValidPort('ip:127.5.0.111', ports)).toBeTruthy()
expect(isIpOrValidPort('ip:', ports)).toBeFalsy()
expect(isIpOrValidPort('ip', ports)).toBeFalsy()
})
test('random', () => {
expect(isIpOrValidPort('file:', ports)).toBeFalsy()
expect(isIpOrValidPort('', ports)).toBeFalsy()
})
})

describe('isValidLocation', () => {
test('port', () => {
expect(isValidLocation('port:/dev/ttyS0', ports)).toBeTruthy()
expect(isValidLocation('port:/dev/ttyS5', ports)).toBeFalsy()
expect(isValidLocation('port:', ports)).toBeFalsy()
expect(isValidLocation('port', ports)).toBeFalsy()
})
test('ip', () => {
expect(isValidLocation('ip:example.com', ports)).toBeTruthy()
expect(isValidLocation('ip:example', ports)).toBeTruthy()
expect(isValidLocation('ip:127.5.0.111', ports)).toBeTruthy()
expect(isValidLocation('ip:127.5.0.4111', ports)).toBeTruthy() // Host not IP.
expect(isValidLocation('ip:[2561:1900:4545:0003:0200:F8FF:FE21:67CF]', ports)).toBeTruthy()
expect(isValidLocation('ip:[2260:F3A4:32CB:715D:5D11:D837:FC76:12FC]', ports)).toBeTruthy()
expect(isValidLocation('ip:[FE80::2045:FAEB:33AF:8374]', ports)).toBeTruthy()
expect(isValidLocation('ip:[::2045:FAEB:33AF:8374]', ports)).toBeTruthy()
expect(isValidLocation('ip:[FE80:2045:FAEB:33AF::]', ports)).toBeTruthy()
expect(isValidLocation('ip:[::11.22.33.44]', ports)).toBeTruthy()
expect(isValidLocation('ip:[F:F:F:F:F:F:192.168.0.1]', ports)).toBeTruthy()
expect(isValidLocation('ip:[2001:db8::123.123.123.123]', ports)).toBeTruthy()
expect(isValidLocation('ip:[2001:db8::123.123.123.123]:55', ports)).toBeTruthy()
expect(isValidLocation('ip:[2001:db8::123.123.123.123]:ab', ports)).toBeFalsy()
expect(isValidLocation('ip:[::]:59', ports)).toBeTruthy()
expect(isValidLocation('ip:[]:59', ports)).toBeFalsy()
expect(isValidLocation('ip://', ports)).toBeFalsy()
expect(isValidLocation('ip:', ports)).toBeFalsy()
})
test('random', () => {
expect(isValidLocation('file:', ports)).toBeFalsy()
expect(isValidLocation('', ports)).toBeFalsy()
})
})
4 changes: 4 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@ export default defineConfig({
'src/main/routes/**/*.ts',
'src/main/routes/**/*.ts',
'src/main/services/rpc/**/*.ts',
// Not going to test the core URL module till it's used.
'src/core/url.ts',
// Not going to test or cover the UI right now.
// TODO: There are some parts that do need coverage:
// - Import and export.
'src/renderer/services/**/*.ts',
'src/renderer/**/*.ts',
'src/renderer/**/*.tsx',
'src/renderer/**/*.vue',
// No real need to test the support framework.
'src/tests/support/**/*.ts',
// The configurations don't need any testing or converage.
'electron.vite.config.ts',
...coverageConfigDefaults.exclude
Expand Down

0 comments on commit 5bae111

Please sign in to comment.