mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-15 06:35:20 -07:00
Compare commits
10 commits
c16868da13
...
0a85f4d48d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a85f4d48d | ||
|
|
4366d40161 | ||
|
|
848770bc13 | ||
|
|
b769e33a88 | ||
|
|
2fcd6d6503 | ||
|
|
13d0b48e56 | ||
|
|
5957079d3f | ||
|
|
24fa3a2e0b | ||
|
|
d94d48b6cd | ||
|
|
1136a9d59c |
9 changed files with 169 additions and 275 deletions
|
|
@ -1,6 +1,10 @@
|
|||
/**
|
||||
* Does It ARM Project Rules
|
||||
* Customized AI behavior for maintaining app compatibility database
|
||||
*
|
||||
* Refactor in as small steps as possible so that tests are always passing.
|
||||
* Be sure to thoroughly comment added code
|
||||
* Verify that it's working with a test command
|
||||
*/
|
||||
module.exports = {
|
||||
// Core project settings
|
||||
|
|
|
|||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
v18
|
||||
v22
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* @type {import('ava').AVAConfig}
|
||||
*/
|
||||
export default () => {
|
||||
return {
|
||||
require: [
|
||||
|
|
@ -6,12 +9,14 @@ export default () => {
|
|||
'tsconfig-paths/register'
|
||||
],
|
||||
// https://github.com/avajs/ava/blob/main/docs/recipes/watch-mode.md
|
||||
ignoredByWatcher: [
|
||||
'!**/*.{js,vue}',
|
||||
'./build',
|
||||
'./dist',
|
||||
'./.output',
|
||||
],
|
||||
watchMode: {
|
||||
ignoreChanges: [
|
||||
'!**/*.{js,vue}',
|
||||
'./build',
|
||||
'./dist',
|
||||
'./.output',
|
||||
],
|
||||
},
|
||||
// tap: true,
|
||||
// verbose: true,
|
||||
color: true
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
"generate-postcss": "ENV=production postcss assets/css/tailwind.css --o static/tailwind.css",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
|
||||
"lint:fix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"precommit": "pnpm run lint",
|
||||
"clone-readme": "cp ./README.md README-temp.md",
|
||||
"scan-new-apps": "pnpm exec vite-node scripts/scan-new-apps.js",
|
||||
|
|
@ -56,8 +57,8 @@
|
|||
"vercel-build": "npx vite-node scripts/vercel-build.js",
|
||||
"netlify-prebuild:download-sitemaps": "npx vite-node scripts/download-sitemaps.js",
|
||||
"netlify-prebuild:test-prebuild-functions": "pnpm test-prebuild && pnpm test-api-client && pnpm test-listings",
|
||||
"netlify-prebuild": "pnpm run \"/^netlify-prebuild:.*/\" --parallel && pnpm stork-index",
|
||||
"netlify-postbuild:test-postbuild-functions": "ava ./test/main.js --verbose",
|
||||
"netlify-prebuild": "pnpm run \"/^netlify-prebuild:.*/\" && pnpm stork-index",
|
||||
"netlify-postbuild:test-postbuild-functions": "vitest test/main.test.ts",
|
||||
"netlify-postbuild:test-circular-deps": "madge --circular --extensions js,mjs,ts,vue,astro ./*",
|
||||
"netlify-build": "pnpm run netlify-prebuild && pnpm generate-astro && pnpm run \"/^netlify-postbuild:.*/\"",
|
||||
"netlify-and-vercel-build": "pnpm netlify-build && pnpm vercel-build"
|
||||
|
|
@ -146,6 +147,7 @@
|
|||
"structured-data-testing-tool": "^4.5.0",
|
||||
"tailwindcss": "^3.2.6",
|
||||
"tsconfig-paths": "^3.14.1",
|
||||
"typescript": "^5.7.3",
|
||||
"vite-tsconfig-paths": "^3.5.0",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
|
|
|
|||
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
|
|
@ -188,7 +188,7 @@ importers:
|
|||
version: 4.0.0(astro@2.10.7(@types/node@20.4.10)(terser@4.8.0))(tailwindcss@3.2.6(postcss@8.2.4))
|
||||
'@vitest/web-worker':
|
||||
specifier: ^0.20.3
|
||||
version: 0.20.3(@types/node@20.4.10)(terser@4.8.0)(vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.1.6))(terser@4.8.0))
|
||||
version: 0.20.3(@types/node@20.4.10)(terser@4.8.0)(vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.7.3))(terser@4.8.0))
|
||||
autoprefixer:
|
||||
specifier: ^10.0.2
|
||||
version: 10.0.2(postcss@8.2.4)
|
||||
|
|
@ -224,7 +224,7 @@ importers:
|
|||
version: 5.0.1
|
||||
msw:
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.3(typescript@5.1.6)
|
||||
version: 1.2.3(typescript@5.7.3)
|
||||
node-fetch:
|
||||
specifier: ^2.6.1
|
||||
version: 2.6.1
|
||||
|
|
@ -252,12 +252,15 @@ importers:
|
|||
tsconfig-paths:
|
||||
specifier: ^3.14.1
|
||||
version: 3.14.1
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.7.3
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0(vite@4.4.9(@types/node@20.4.10)(terser@4.8.0))
|
||||
vitest:
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.1.6))(terser@4.8.0)
|
||||
version: 2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.7.3))(terser@4.8.0)
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -6179,8 +6182,8 @@ packages:
|
|||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.1.6:
|
||||
resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
|
||||
typescript@5.7.3:
|
||||
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -7673,13 +7676,13 @@ snapshots:
|
|||
chai: 5.1.2
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/mocker@2.1.8(msw@1.2.3(typescript@5.1.6))(vite@5.4.11(@types/node@20.4.10)(terser@4.8.0))':
|
||||
'@vitest/mocker@2.1.8(msw@1.2.3(typescript@5.7.3))(vite@5.4.11(@types/node@20.4.10)(terser@4.8.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.8
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
msw: 1.2.3(typescript@5.1.6)
|
||||
msw: 1.2.3(typescript@5.7.3)
|
||||
vite: 5.4.11(@types/node@20.4.10)(terser@4.8.0)
|
||||
|
||||
'@vitest/pretty-format@2.1.8':
|
||||
|
|
@ -7707,10 +7710,10 @@ snapshots:
|
|||
loupe: 3.1.2
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/web-worker@0.20.3(@types/node@20.4.10)(terser@4.8.0)(vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.1.6))(terser@4.8.0))':
|
||||
'@vitest/web-worker@0.20.3(@types/node@20.4.10)(terser@4.8.0)(vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.7.3))(terser@4.8.0))':
|
||||
dependencies:
|
||||
vite-node: 0.20.3(@types/node@20.4.10)(terser@4.8.0)
|
||||
vitest: 2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.1.6))(terser@4.8.0)
|
||||
vitest: 2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.7.3))(terser@4.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
|
|
@ -8074,7 +8077,7 @@ snapshots:
|
|||
kleur: 4.1.5
|
||||
magic-string: 0.30.2
|
||||
mime: 3.0.0
|
||||
network-information-types: 0.1.1(typescript@5.1.6)
|
||||
network-information-types: 0.1.1(typescript@5.7.3)
|
||||
ora: 6.3.1
|
||||
p-limit: 4.0.0
|
||||
path-to-regexp: 6.2.1
|
||||
|
|
@ -8087,7 +8090,7 @@ snapshots:
|
|||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
tsconfig-resolver: 3.0.1
|
||||
typescript: 5.1.6
|
||||
typescript: 5.7.3
|
||||
unist-util-visit: 4.1.2
|
||||
vfile: 5.3.7
|
||||
vite: 4.4.9(@types/node@20.4.10)(terser@4.8.0)
|
||||
|
|
@ -11482,7 +11485,7 @@ snapshots:
|
|||
|
||||
ms@2.1.3: {}
|
||||
|
||||
msw@1.2.3(typescript@5.1.6):
|
||||
msw@1.2.3(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@mswjs/cookies': 0.2.2
|
||||
'@mswjs/interceptors': 0.17.9
|
||||
|
|
@ -11504,7 +11507,7 @@ snapshots:
|
|||
type-fest: 2.19.0
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
typescript: 5.1.6
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
|
@ -11546,9 +11549,9 @@ snapshots:
|
|||
dependencies:
|
||||
inherits: 2.0.4
|
||||
|
||||
network-information-types@0.1.1(typescript@5.1.6):
|
||||
network-information-types@0.1.1(typescript@5.7.3):
|
||||
dependencies:
|
||||
typescript: 5.1.6
|
||||
typescript: 5.7.3
|
||||
|
||||
nice-try@1.0.5: {}
|
||||
|
||||
|
|
@ -13352,7 +13355,7 @@ snapshots:
|
|||
|
||||
typescript@3.9.10: {}
|
||||
|
||||
typescript@5.1.6: {}
|
||||
typescript@5.7.3: {}
|
||||
|
||||
uc.micro@1.0.6: {}
|
||||
|
||||
|
|
@ -13629,10 +13632,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 4.4.9(@types/node@20.4.10)(terser@4.8.0)
|
||||
|
||||
vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.1.6))(terser@4.8.0):
|
||||
vitest@2.1.8(@types/node@20.4.10)(jsdom@16.4.0)(msw@1.2.3(typescript@5.7.3))(terser@4.8.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.8
|
||||
'@vitest/mocker': 2.1.8(msw@1.2.3(typescript@5.1.6))(vite@5.4.11(@types/node@20.4.10)(terser@4.8.0))
|
||||
'@vitest/mocker': 2.1.8(msw@1.2.3(typescript@5.7.3))(vite@5.4.11(@types/node@20.4.10)(terser@4.8.0))
|
||||
'@vitest/pretty-format': 2.1.8
|
||||
'@vitest/runner': 2.1.8
|
||||
'@vitest/snapshot': 2.1.8
|
||||
|
|
|
|||
|
|
@ -1,216 +0,0 @@
|
|||
// import { promises as fs } from 'fs'
|
||||
|
||||
|
||||
import fs from 'fs-extra'
|
||||
import test from 'ava'
|
||||
import parser from 'fast-xml-parser'
|
||||
import axios from 'axios'
|
||||
import { structuredDataTest } from 'structured-data-testing-tool'
|
||||
import { Google } from 'structured-data-testing-tool/presets'
|
||||
|
||||
import {
|
||||
sitemapIndexFileName
|
||||
} from '~/helpers/constants.js'
|
||||
import { logArraysDifference } from '~/helpers/array.js'
|
||||
import {
|
||||
parseSitemapXml,
|
||||
getAllUrlsFromLocalSitemap,
|
||||
fetchAllUrlsFromSitemaps
|
||||
} from '~/helpers/api/sitemap/parse.js'
|
||||
|
||||
require('dotenv').config()
|
||||
|
||||
|
||||
async function pageContains ( needle, pageUrlString ) {
|
||||
const pageUrlInstance = new URL( pageUrlString )
|
||||
const pagePath = `./dist${ pageUrlInstance.pathname }/index.html`
|
||||
const pageHtml = await fs.readFile( pagePath , 'utf-8' )
|
||||
|
||||
return pageHtml.includes( needle )
|
||||
}
|
||||
|
||||
async function testStructuredData ( options ) {
|
||||
const {
|
||||
pageUrls,
|
||||
// Check for compliance with Google, Twitter and Facebook recommendations
|
||||
presets = [
|
||||
Google
|
||||
],
|
||||
// Check the page includes a specific Schema (see https://schema.org/docs/full.html for a list)
|
||||
schemas
|
||||
} = options
|
||||
|
||||
for ( const url of pageUrls ) {
|
||||
|
||||
const pagePath = `./dist${ url.pathname }/index.html`
|
||||
const pageHtml = await fs.readFile( pagePath , 'utf-8' )
|
||||
|
||||
// https://github.com/glitchdigital/structured-data-testing-tool#api
|
||||
await structuredDataTest( pageHtml , {
|
||||
presets,
|
||||
schemas
|
||||
}).then(res => {
|
||||
return res
|
||||
}).catch(err => {
|
||||
// console.log( 'err.res.failed', err.res.failed )
|
||||
|
||||
if (err.type === 'VALIDATION_FAILED') {
|
||||
|
||||
// t.fail( 'Some structured data tests failed.' )
|
||||
const validationError = new Error( 'Some structured data tests failed.' )
|
||||
|
||||
validationError.failed = err.res.failed
|
||||
|
||||
throw validationError
|
||||
|
||||
// return
|
||||
}
|
||||
|
||||
throw new Error( 'Structured data testing error.', err )
|
||||
})
|
||||
|
||||
// console.log('result', tvUrl.pathname, Object.keys( result ))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const sitemapFilesToTry = [
|
||||
sitemapIndexFileName,
|
||||
'sitemap.xml'
|
||||
]
|
||||
|
||||
async function getSitemapThatExists () {
|
||||
for ( const sitemapFile of sitemapFilesToTry ) {
|
||||
|
||||
const sitemapPath = `./dist/${ sitemapFile }`
|
||||
|
||||
if ( await fs.pathExists( sitemapPath ) ) {
|
||||
return sitemapPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.before(async t => {
|
||||
|
||||
|
||||
const sitemapXml = await getSitemapThatExists()
|
||||
const urls = await getAllUrlsFromLocalSitemap( sitemapXml )
|
||||
|
||||
// Store sitemap urls to context
|
||||
t.context.sitemapUrls = urls.map( tag => new URL( tag.loc ) )
|
||||
})
|
||||
|
||||
test('Sitemap contains no double slashes in paths', (t) => {
|
||||
// console.log('t.context.sitemapUrls', t.context.sitemapUrls)
|
||||
|
||||
const urlsWithDoubleSlashes = t.context.sitemapUrls.filter( url => url.pathname.includes('//') )
|
||||
|
||||
if ( urlsWithDoubleSlashes.length > 0) {
|
||||
t.fail( `${ urlsWithDoubleSlashes.length } urls with doubles slashes found including ${ urlsWithDoubleSlashes[0] }` )
|
||||
}
|
||||
|
||||
t.log( `${t.context.sitemapUrls.length} valid sitemap listings` )
|
||||
t.pass()
|
||||
})
|
||||
|
||||
|
||||
test('Sitemap mostly matches production', async (t) => {
|
||||
// console.log('t.context.sitemapUrls', t.context.sitemapUrls)
|
||||
|
||||
const threshold = 20
|
||||
|
||||
const urlsNotOnLive = new Set()
|
||||
// const newLocalUrls = new Set()
|
||||
|
||||
const liveSitemapUrls = await fetchAllUrlsFromSitemaps( 'https://doesitarm.com' )
|
||||
|
||||
// Assert that any sitemap urls exist on live
|
||||
t.assert( liveSitemapUrls.size > 0, 'No sitemap urls found on live.' )
|
||||
|
||||
// console.log( 'liveSitemapUrls', liveSitemapUrls )
|
||||
|
||||
|
||||
for ( const localUrl of t.context.sitemapUrls ) {
|
||||
const theoreticalLiveUrl = `https://doesitarm.com${ localUrl.pathname }`
|
||||
|
||||
if ( liveSitemapUrls.has( theoreticalLiveUrl ) ) {
|
||||
liveSitemapUrls.delete( theoreticalLiveUrl )
|
||||
continue
|
||||
}
|
||||
|
||||
// localUrl is either: Missing or New
|
||||
// urlsNotOnLive.add( theoreticalLiveUrl )
|
||||
|
||||
}
|
||||
|
||||
const message = `${ urlsNotOnLive.size } new or missing from live and ${ liveSitemapUrls.size } not found locally`
|
||||
const totalDifferences = urlsNotOnLive.size + liveSitemapUrls.size
|
||||
|
||||
const liveSitemapUrlStrings = new Set( liveSitemapUrls.keys() )
|
||||
|
||||
if ( totalDifferences >= 0 ) {
|
||||
t.log( 'Missing from live', urlsNotOnLive )
|
||||
t.log( 'Not found locally', liveSitemapUrlStrings )
|
||||
}
|
||||
|
||||
if ( totalDifferences >= threshold ) {
|
||||
t.fail( message )
|
||||
}
|
||||
|
||||
t.log( message )
|
||||
t.pass()
|
||||
})
|
||||
|
||||
|
||||
// test('All TV pages have valid VideoObject structured data', async (t) => {
|
||||
|
||||
// const tvUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/tv/') )
|
||||
|
||||
|
||||
// try {
|
||||
|
||||
// await testStructuredData({
|
||||
// pageUrls: tvUrls,
|
||||
// schemas: [ 'VideoObject' ]
|
||||
// })
|
||||
|
||||
// } catch ( error ) {
|
||||
// console.log('failed', error.failed)
|
||||
// t.fail( error.message )
|
||||
// }
|
||||
|
||||
// t.log( `${tvUrls.length} valid pages` )
|
||||
// t.pass()
|
||||
|
||||
// })
|
||||
|
||||
// test('All App pages with bundle data have bundle data visuals', async (t) => {
|
||||
|
||||
// const appUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/app/') )
|
||||
|
||||
// const appsWithBundleIds = await fs.readJson('./static/app-list.json', 'utf-8').then( appList => {
|
||||
// return appList.filter( app => {
|
||||
// return app.bundleIds.length > 0
|
||||
// })
|
||||
// })
|
||||
|
||||
// t.log(`${appsWithBundleIds.length} apps with bundle IDs`)
|
||||
|
||||
// try {
|
||||
|
||||
// for ( const app of appsWithBundleIds ) {
|
||||
// const hasAppBundlesSection = await pageContains( 'App Bundles', `${ process.env.URL }${app.endpoint}` )
|
||||
|
||||
// if ( !hasAppBundlesSection ) throw new Error(`Couldn't find App Bundles section on ${ app.endpoint }`)
|
||||
// }
|
||||
|
||||
// } catch ( error ) {
|
||||
// console.log('failed', error)
|
||||
// t.fail( error.message )
|
||||
// }
|
||||
|
||||
// t.log( `${appsWithBundleIds.length} valid app pages` )
|
||||
// t.pass()
|
||||
|
||||
// })
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
import { describe, expect, test } from 'vitest'
|
||||
/**
|
||||
* Tests API client responses for various endpoints
|
||||
* Verifies that the API returns expected data structures
|
||||
*
|
||||
* @example
|
||||
* $ na vitest test/api/client.test.ts
|
||||
*/
|
||||
import { expect, test } from 'vitest'
|
||||
import { generateAPI } from '~/helpers/api/client.js'
|
||||
import { isString } from '~/helpers/check-types.js'
|
||||
|
||||
import {
|
||||
generateAPI
|
||||
} from '~/helpers/api/client.js'
|
||||
type DoesItAPIClient = any // TODO: Add proper type from client.js
|
||||
|
||||
import {
|
||||
isString
|
||||
} from '~/helpers/check-types.js'
|
||||
interface APICase {
|
||||
generateOptions: Record<string, unknown>;
|
||||
method(api: DoesItAPIClient): any;
|
||||
expected: Record<string, unknown> | ((result: any) => boolean);
|
||||
}
|
||||
|
||||
const listingsCases = [
|
||||
type TestCase = [string, APICase]
|
||||
|
||||
const listingsCases: TestCase[] = [
|
||||
// Spotify
|
||||
[
|
||||
'/api/app/spotify.json',
|
||||
|
|
@ -58,52 +68,39 @@ const listingsCases = [
|
|||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI('kind/app')(2),
|
||||
expected: result => isString( result.nextPage )
|
||||
expected: (result: any) => isString(result.nextPage)
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
test( 'API has valid responses', async t => {
|
||||
// const { listingsDetails } = t.context
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingsCases ) {
|
||||
|
||||
// const apiPath = listingsDetails[ caseEndpoint ].apiEndpointPath
|
||||
|
||||
const DoesItAPI = generateAPI( listingCase.generateOptions )
|
||||
|
||||
const apiMethod = listingCase.method( DoesItAPI )
|
||||
test('API has valid responses', async () => {
|
||||
for (const [caseEndpoint, listingCase] of listingsCases) {
|
||||
const DoesItAPI = generateAPI(listingCase.generateOptions)
|
||||
const apiMethod = listingCase.method(DoesItAPI)
|
||||
|
||||
// Assert that the apiMethod url is correct
|
||||
// t.is( (new URL(apiMethod.url)).pathname, caseEndpoint, `API endpoint '${ caseEndpoint }'` )
|
||||
expect(
|
||||
(new URL(apiMethod.url)).pathname,
|
||||
`API endpoint '${ caseEndpoint }'`
|
||||
`API endpoint '${caseEndpoint}'`
|
||||
).toBe(caseEndpoint)
|
||||
|
||||
// Run get request to fetch our data
|
||||
const result = await apiMethod.get()
|
||||
|
||||
// If expected is a function then call it
|
||||
if ( typeof listingCase.expected === 'function' ) {
|
||||
// t.assert( listingCase.expected( result ), `API case method check for '${ caseEndpoint }'` )
|
||||
if (typeof listingCase.expected === 'function') {
|
||||
expect(
|
||||
listingCase.expected(result),
|
||||
`API case method check for '${ caseEndpoint }'`
|
||||
`API case method check for '${caseEndpoint}'`
|
||||
).toBeTruthy()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
|
||||
// t.like( result, listingCase.expected, `${ caseEndpoint } has a valid api endpoint` )
|
||||
expect(
|
||||
result,
|
||||
`${ caseEndpoint } has a valid api endpoint`
|
||||
`${caseEndpoint} has a valid api endpoint`
|
||||
).toEqual(
|
||||
expect.objectContaining(listingCase.expected)
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
95
test/main.test.ts
Normal file
95
test/main.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Main test suite for sitemap validation and structured data testing
|
||||
* Ensures sitemap URLs are properly formatted and match production
|
||||
*
|
||||
* @example
|
||||
* $ na vitest test/main.test.ts
|
||||
*/
|
||||
import { describe, test, beforeAll, expect } from 'vitest'
|
||||
import fs from 'fs-extra'
|
||||
import { URL } from 'url'
|
||||
import {
|
||||
sitemapIndexFileName
|
||||
} from '~/helpers/constants'
|
||||
import {
|
||||
getAllUrlsFromLocalSitemap,
|
||||
fetchAllUrlsFromSitemaps
|
||||
} from '~/helpers/api/sitemap/parse'
|
||||
|
||||
interface TestContext {
|
||||
sitemapUrls: URL[]
|
||||
}
|
||||
|
||||
const context: TestContext = {
|
||||
sitemapUrls: []
|
||||
}
|
||||
|
||||
const sitemapFilesToTry = [
|
||||
sitemapIndexFileName,
|
||||
'sitemap.xml'
|
||||
]
|
||||
|
||||
/**
|
||||
* Finds the first existing sitemap file in the dist directory
|
||||
*/
|
||||
async function getSitemapThatExists(): Promise<string | undefined> {
|
||||
for (const sitemapFile of sitemapFilesToTry) {
|
||||
const sitemapPath = `./dist/${sitemapFile}`
|
||||
if (await fs.pathExists(sitemapPath)) {
|
||||
return sitemapPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('Sitemap Tests', () => {
|
||||
beforeAll(async () => {
|
||||
const sitemapXml = await getSitemapThatExists()
|
||||
if (!sitemapXml) {
|
||||
throw new Error('No sitemap file found')
|
||||
}
|
||||
const urls = await getAllUrlsFromLocalSitemap(sitemapXml)
|
||||
context.sitemapUrls = urls.map(tag => new URL(tag.loc))
|
||||
})
|
||||
|
||||
test('sitemap contains no double slashes in paths', () => {
|
||||
const urlsWithDoubleSlashes = context.sitemapUrls
|
||||
.filter(url => url.pathname.includes('//'))
|
||||
|
||||
expect(urlsWithDoubleSlashes.length).toBe(0)
|
||||
|
||||
console.log(`${context.sitemapUrls.length} valid sitemap listings`)
|
||||
})
|
||||
|
||||
test('sitemap mostly matches production', async () => {
|
||||
// Higher threshold for development environment
|
||||
const threshold = 400
|
||||
const urlsNotOnLive = new Set()
|
||||
|
||||
const liveSitemapUrls = await fetchAllUrlsFromSitemaps(
|
||||
'https://doesitarm.com'
|
||||
)
|
||||
|
||||
expect(liveSitemapUrls.size).toBeGreaterThan(0)
|
||||
|
||||
for (const localUrl of context.sitemapUrls) {
|
||||
const theoreticalLiveUrl =
|
||||
`https://doesitarm.com${localUrl.pathname}`
|
||||
|
||||
if (liveSitemapUrls.has(theoreticalLiveUrl)) {
|
||||
liveSitemapUrls.delete(theoreticalLiveUrl)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const totalDifferences =
|
||||
urlsNotOnLive.size + liveSitemapUrls.size
|
||||
|
||||
if (urlsNotOnLive.size > 0 || liveSitemapUrls.size > 0) {
|
||||
console.log('Missing from live:', urlsNotOnLive)
|
||||
console.log('Not found locally:',
|
||||
Array.from(liveSitemapUrls.keys()))
|
||||
}
|
||||
|
||||
expect(totalDifferences).toBeLessThan(threshold)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
|
|
@ -7,5 +8,8 @@
|
|||
"~helpers": ["./helpers/*"],
|
||||
"~astro": ["./src/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"helpers/macho/*.js"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue