Skip to content

Commit

Permalink
chore: github actions workflow for tests / visual regressions (#21)
Browse files Browse the repository at this point in the history
* chore: github actions workflow for tests / visual regressions

* chore: github actions workflow for tests / visual regressions

* chore: github actions workflow for tests / visual regressions

* chore: github actions workflow for tests / visual regressions

* chore: tidying up directories for generated files

* chore: generate content-metadata file

* chore: ignoring linting on generated files

* chore: simplifying NPM generate scripts

* chore: bump Node down to LTS

* chore: switching require to ES import
  • Loading branch information
holloway authored Dec 16, 2024
1 parent d00d639 commit 401e5bc
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 19 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Vitest, TypeScript check, ESLint

on: [push, workflow_dispatch]

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: './client'

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 22.x
cache: 'npm'
cache-dependency-path: '**/package-lock.json'

- name: Install dependencies
run: npm ci
working-directory: ./client

- name: Generate content-metadata
run: npm run generate:content-metadata
working-directory: ./client

- name: Run all other tests
run: npm run test
working-directory: ./client

File renamed without changes.
1 change: 0 additions & 1 deletion client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ logs
# generated by generate-content-metadata.ts
content-metadata.json

generated
2 changes: 1 addition & 1 deletion client/components/ContentDocLastUpdated.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { DateTime } from 'luxon'
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
import _contentMetadata from '../content-metadata.json'
import _contentMetadata from '../generated/content-metadata.json'
import type { ContentMetadata } from '~/scripts/generate-content-metadata'
const contentMetadata: ContentMetadata = _contentMetadata
Expand Down
6 changes: 4 additions & 2 deletions client/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'

export default withNuxt({
export default withNuxt([
{ ignores: ['**/generated/'] },
{
rules: {
'vue/comma-dangle': 'off',
'vue/html-closing-bracket-newline': 'off',
Expand Down Expand Up @@ -33,4 +35,4 @@ export default withNuxt({
}
]
}
})
}])
2 changes: 2 additions & 0 deletions client/generated/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
content-metadata.json
# red-client.ts
323 changes: 323 additions & 0 deletions client/generated/red-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
// Auto-generated by https://github.com/vladkens/apigen-ts
// Source: http://localhost:8000/api/schema/

type Headers = Record<string, string>
export type ApigenHeaders =
| Headers
| ((method: string, path: string) => Headers | Promise<Headers>)

export interface ApigenConfig {
baseUrl: string
headers: ApigenHeaders
}

export interface ApigenRequest extends Omit<RequestInit, 'body'> {
search?: Record<string, unknown>
body?: unknown
}

export class ApiClient {
ISO_FORMAT =
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?(?:[-+]\d{2}:?\d{2}|Z)?$/
Config: ApigenConfig

constructor(config?: Partial<ApigenConfig>) {
this.Config = { baseUrl: '/', headers: {}, ...config }
}

PopulateDates<T>(d: T): T {
if (d === null || d === undefined || typeof d !== 'object') return d

const t = d as unknown as Record<string, unknown>
for (const [k, v] of Object.entries(t)) {
if (typeof v === 'string' && this.ISO_FORMAT.test(v)) t[k] = new Date(v)
else if (typeof v === 'object') this.PopulateDates(v)
}

return d
}

async ParseError(rep: Response) {
try {
return await rep.json()
} catch (e) {
throw rep
}
}

PrepareFetchUrl(path: string): URL {
let base = this.Config.baseUrl
if ('location' in globalThis && (base === '' || base.startsWith('/'))) {
const { location } = globalThis as unknown as {
location: { origin: string }
}
base = `${location.origin}${base.endsWith('/') ? base : `/${base}`}`
}

return new URL(path, base)
}

async Fetch<T>(
method: string,
path: string,
opts: ApigenRequest = {}
): Promise<T> {
const url = this.PrepareFetchUrl(path)

for (const [k, v] of Object.entries(opts?.search ?? {})) {
url.searchParams.append(k, Array.isArray(v) ? v.join(',') : (v as string))
}

const configHeaders =
typeof this.Config.headers === 'function' ?
await this.Config.headers(method, path)
: this.Config.headers

const headers = new Headers({ ...configHeaders, ...opts.headers })
const ct = headers.get('content-type') ?? 'application/json'

let body: FormData | URLSearchParams | string | undefined = undefined

if (
ct === 'multipart/form-data' ||
ct === 'application/x-www-form-urlencoded'
) {
headers.delete('content-type')
body =
ct === 'multipart/form-data' ? new FormData() : new URLSearchParams()
for (const [k, v] of Object.entries(
opts.body as Record<string, string>
)) {
body.append(k, v)
}
}

if (ct === 'application/json' && typeof opts.body !== 'string') {
headers.set('content-type', 'application/json')
body = JSON.stringify(opts.body)
}

const credentials = opts.credentials ?? 'include'
const rep = await fetch(url.toString(), {
method,
...opts,
headers,
body,
credentials
})
if (!rep.ok) throw await this.ParseError(rep)

const rs = await rep.text()
try {
return this.PopulateDates(JSON.parse(rs) as T)
} catch (e) {
return rs as unknown as T
}
}

red = {
docList: (search: {
area?: string[]
group?: string[]
limit?: number
offset?: number
published_after?: string
published_before?: string
search?: string
sort?: ('-number' | '-published' | 'number' | 'published')[]
status?: (
| 'bcp'
| 'experimental'
| 'historic'
| 'informational'
| 'standard'
| 'unknown'
)[]
stream?: string[]
}) => {
return this.Fetch<PaginatedRfcMetadataList>('get', '/api/red/doc/', {
search
})
},

docRetrieve: (id: number) => {
return this.Fetch<RfcMetadata>('get', `/api/red/doc/${id}/`, {})
}
}

schema = {
retrieve: (search: { format?: 'json' | 'yaml' }) => {
return this.Fetch<Record<string, unknown>>('get', '/api/schema/', {
search
})
}
}
}

export enum ClientErrorEnum {
Client_error = 'client_error'
}

export type DocIdentifier = {
type: DocIdentifierTypeEnum
value: string
}

export enum DocIdentifierTypeEnum {
Doi = 'doi',
Issn = 'issn'
}

export type Error404 = {
code: ErrorCode404Enum
detail: string
attr: string | null
}

export enum ErrorCode404Enum {
Not_found = 'not_found'
}

export type ErrorResponse404 = {
type: ClientErrorEnum
errors: Error404[]
}

export type Group = {
acronym: string
name: string
}

export type PaginatedRfcMetadataList = {
count: number
next?: string | null
previous?: string | null
results: RfcMetadata[]
}

export type ParseError = {
code: ParseErrorCodeEnum
detail: string
attr: string | null
}

export enum ParseErrorCodeEnum {
Parse_error = 'parse_error'
}

export type ParseErrorResponse = {
type: ClientErrorEnum
errors: ParseError[]
}

export type RedDocListAreaErrorComponent = {
attr: 'area'
code: 'invalid_choice' | 'invalid_list' | 'invalid_pk_value'
detail: string
}

export type RedDocListError =
| RedDocListPublishedErrorComponent
| RedDocListStreamErrorComponent
| RedDocListGroupErrorComponent
| RedDocListAreaErrorComponent
| RedDocListStatusErrorComponent
| RedDocListSortErrorComponent

export type RedDocListErrorResponse400 =
| RedDocListValidationError
| ParseErrorResponse

export type RedDocListGroupErrorComponent = {
attr: 'group'
code: 'invalid_choice' | 'invalid_list' | 'invalid_pk_value'
detail: string
}

export type RedDocListPublishedErrorComponent = {
attr: 'published'
code: 'invalid'
detail: string
}

export type RedDocListSortErrorComponent = {
attr: 'sort'
code: 'invalid_choice'
detail: string
}

export type RedDocListStatusErrorComponent = {
attr: 'status'
code: 'invalid_choice' | 'invalid_list'
detail: string
}

export type RedDocListStreamErrorComponent = {
attr: 'stream'
code: 'invalid_choice' | 'invalid_list' | 'invalid_pk_value'
detail: string
}

export type RedDocListValidationError = {
type: ValidationErrorEnum
errors: RedDocListError[]
}

export type RedDocRetrieveErrorResponse400 = ParseErrorResponse

export type RelatedRfc = {
id: number
number: number
title: string
}

export type RfcAuthor = {
person: number
name: string
email?: string
affiliation?: string
country?: string
}

export type RfcMetadata = {
id?: number
number: number
title: string
published: string
status: RfcStatus
pages?: number | null
authors: RfcAuthor[]
group: Group
area?: Group
stream: StreamName
identifiers?: DocIdentifier
obsoleted_by?: RelatedRfc[]
updated_by?: RelatedRfc[]
abstract?: string
}

export type RfcStatus = {
slug: SlugEnum
name: string
}

export type SchemaRetrieveErrorResponse400 = ParseErrorResponse

export enum SlugEnum {
Bcp = 'bcp',
Experimental = 'experimental',
Historic = 'historic',
Informational = 'informational',
Standard = 'standard',
Unknown = 'unknown'
}

export type StreamName = {
slug: string
name: string
desc?: string
}

export enum ValidationErrorEnum {
Validation_error = 'validation_error'
}
Loading

0 comments on commit 401e5bc

Please sign in to comment.