mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-18 06:44:46 -07:00
update: disable ava test suites
This commit is contained in:
parent
44d7393039
commit
fddfa9d5a4
7 changed files with 0 additions and 3 deletions
94
test/_disabled/api/client.js
Normal file
94
test/_disabled/api/client.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import test from 'ava'
|
||||
|
||||
import {
|
||||
generateAPI
|
||||
} from '~/helpers/api/client.js'
|
||||
|
||||
import {
|
||||
isString
|
||||
} from '~/helpers/check-types.js'
|
||||
|
||||
const listingsCases = [
|
||||
|
||||
// Spotify
|
||||
[
|
||||
'/api/app/spotify.json',
|
||||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI.app.spotify,
|
||||
expected: {
|
||||
name: 'Spotify',
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// Electron
|
||||
[
|
||||
'/api/app/electron-framework.json',
|
||||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI.app('electron-framework'),
|
||||
expected: { name: 'Electron Framework' }
|
||||
}
|
||||
],
|
||||
|
||||
// Express VPN
|
||||
[
|
||||
'/api/app/expressvpn.json',
|
||||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI.app.expressvpn,
|
||||
expected: { name: 'ExpressVPN' }
|
||||
}
|
||||
],
|
||||
|
||||
// Solo App URL
|
||||
[
|
||||
'/api/app/solo.json',
|
||||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI.app('solo'),
|
||||
expected: { name: 'SOLO' }
|
||||
}
|
||||
],
|
||||
|
||||
// Page 2 of App Kinds
|
||||
[
|
||||
'/api/kind/app/2.json',
|
||||
{
|
||||
generateOptions: {},
|
||||
method: DoesItAPI => DoesItAPI('kind/app')(2),
|
||||
expected: result => 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 )
|
||||
|
||||
// Assert that the apiMethod url is correct
|
||||
t.is( (new URL(apiMethod.url)).pathname, caseEndpoint, `API endpoint '${ caseEndpoint }'` )
|
||||
|
||||
// Run get request to fetch our data
|
||||
const result = await apiMethod.get()
|
||||
|
||||
// If expected is a function then call it
|
||||
// Otherwise, compare the result to the expected
|
||||
if ( typeof listingCase.expected === 'function' ) {
|
||||
t.assert( listingCase.expected( result ), `API case method check for '${ caseEndpoint }'` )
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
t.like( result, listingCase.expected, `${ caseEndpoint } has a valid api endpoint` )
|
||||
}
|
||||
})
|
||||
97
test/_disabled/helpers/head.js
Normal file
97
test/_disabled/helpers/head.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import {
|
||||
isValidHttpUrl,
|
||||
isValidImageUrl,
|
||||
isNonEmptyString,
|
||||
isPositiveNumberString
|
||||
} from '~/helpers/check-types.js'
|
||||
|
||||
export const headPropertyTypes = {
|
||||
'meta[charset]': {
|
||||
charset: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
'meta[name="viewport"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta property="og:image" content="https://doesitarm.com/images/og-image.png">
|
||||
'meta[property="og:image"]': {
|
||||
content: isValidImageUrl
|
||||
},
|
||||
|
||||
// <meta property="og:image:width" content="1200">
|
||||
'meta[property="og:image:width"]': {
|
||||
content: isPositiveNumberString
|
||||
},
|
||||
|
||||
// <meta property="og:image:height" content="627">
|
||||
'meta[property="og:image:height"]': {
|
||||
content: isPositiveNumberString
|
||||
},
|
||||
|
||||
// <meta property="og:image:alt" content="Does It ARM Logo">
|
||||
'meta[property="og:image:alt"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta property="twitter:card" content="summary">
|
||||
'meta[property="twitter:card"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta property="twitter:title" content="Does It ARM">
|
||||
'meta[property="twitter:title"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta property="twitter:description" content="Find out the latest app support for Apple Silicon and the Apple M1 Pro and M1 Max Processors">
|
||||
'meta[property="twitter:description"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta property="twitter:url" content="https://doesitarm.com">
|
||||
'meta[property="twitter:url"]': {
|
||||
content: isValidHttpUrl
|
||||
},
|
||||
|
||||
// <meta property="twitter:image" content="https://doesitarm.com/images/mark.png">
|
||||
'meta[property="twitter:image"]': {
|
||||
content: isValidImageUrl,
|
||||
},
|
||||
|
||||
// <meta data-hid="description" name="description" content={ headDescription }>
|
||||
'meta[name="description"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <meta data-hid="twitter:title" property="twitter:title" content="Apple Silicon and Apple M1 Pro and M1 Max app and game compatibility list">
|
||||
'meta[property="twitter:title"]': {
|
||||
content: isNonEmptyString
|
||||
},
|
||||
|
||||
// <link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
'link[rel="icon"]': {
|
||||
href: isNonEmptyString
|
||||
},
|
||||
|
||||
// <!-- Preconnect Assets -->
|
||||
// <link rel="preconnect" href="https://www.googletagmanager.com">
|
||||
// <link rel="preconnect" href="https://cdn.carbonads.com">
|
||||
// <link rel="preconnect" href="https://srv.carbonads.net">
|
||||
// <link rel="preconnect" href="https://cdn4.buysellads.net"></link>
|
||||
'link[rel="preconnect"]': {
|
||||
href: isValidHttpUrl
|
||||
},
|
||||
|
||||
// <link rel="preload" as="image" href="large-image.webp" media="(min-width: 768px)" imagesrcset="large-image.webp, large-image-2x.webp 2x" type="image/webp" />
|
||||
'link[rel="preload"]': {
|
||||
as: isNonEmptyString,
|
||||
href: isValidHttpUrl,
|
||||
media: isNonEmptyString,
|
||||
imagesrcset: isNonEmptyString,
|
||||
type: isNonEmptyString,
|
||||
|
||||
count: false
|
||||
},
|
||||
}
|
||||
248
test/_disabled/listings/index.js
Normal file
248
test/_disabled/listings/index.js
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import fs from 'fs-extra'
|
||||
import has from 'just-has'
|
||||
import test from 'ava'
|
||||
import axios from 'axios'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import { structuredDataTestHtml } from 'structured-data-testing-tool'
|
||||
import { Google } from 'structured-data-testing-tool/presets'
|
||||
|
||||
|
||||
import {
|
||||
makeApiPathFromEndpoint,
|
||||
getVideoImages,
|
||||
ListingDetails
|
||||
} from '~/helpers/listing-page.js'
|
||||
import { headPropertyTypes } from '~/test/helpers/head.js'
|
||||
import { PageHead } from '~/helpers/config-node.js'
|
||||
|
||||
|
||||
|
||||
const listingsCases = {
|
||||
|
||||
// Spotify
|
||||
'/app/spotify': {
|
||||
endpoint: '/app/spotify',
|
||||
apiEndpointPath: '/api/app/spotify.json',
|
||||
expectInitialVideo: true,
|
||||
shouldHaveVideoStucturedData: false,
|
||||
},
|
||||
|
||||
// Electron
|
||||
'/app/electron-framework': {
|
||||
endpoint: '/app/electron-framework',
|
||||
apiEndpointPath: '/api/app/electron-framework.json',
|
||||
expectInitialVideo: false,
|
||||
shouldHaveVideoStucturedData: false,
|
||||
},
|
||||
|
||||
// Express VPN Benchmarks
|
||||
'/app/expressvpn/benchmarks/': {
|
||||
endpoint: '/app/expressvpn/benchmarks/',
|
||||
apiEndpointPath: '/api/app/expressvpn.json',
|
||||
expectInitialVideo: true,
|
||||
shouldHaveVideoStucturedData: true
|
||||
},
|
||||
|
||||
// Express VPN Benchmarks
|
||||
'/tv/install-instagram-app-on-m1-macbook-air-apple-silicon-tutorial-i-vfbmworal6i/': {
|
||||
endpoint: '/tv/install-instagram-app-on-m1-macbook-air-apple-silicon-tutorial-i-vfbmworal6i/',
|
||||
apiEndpointPath: '/api/tv/install-instagram-app-on-m1-macbook-air-apple-silicon-tutorial-i-vfbmworal6i.json',
|
||||
expectInitialVideo: true,
|
||||
shouldHaveVideoStucturedData: true
|
||||
}
|
||||
}
|
||||
|
||||
const listingCaseEntries = Object.entries( listingsCases )
|
||||
|
||||
test.before(async t => {
|
||||
|
||||
t.context.listings = {}
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingCaseEntries ) {
|
||||
const { endpoint } = listingCase
|
||||
|
||||
const apiPath = makeApiPathFromEndpoint( caseEndpoint )
|
||||
const localPath = `./static${ apiPath }`
|
||||
|
||||
// Check if the endpoint exists locally
|
||||
// so we don't have to wait for the API
|
||||
if ( await fs.pathExists( localPath ) ) {
|
||||
console.log('Using local endpoint data for', endpoint)
|
||||
t.context.listings[ caseEndpoint ] = await fs.readJson( localPath )
|
||||
continue
|
||||
}
|
||||
|
||||
const { data } = await axios.get(`${ process.env.PUBLIC_API_DOMAIN }${ apiPath }`)
|
||||
|
||||
t.context.listings[ caseEndpoint ] = data
|
||||
}
|
||||
|
||||
t.context.listingsDetails = {}
|
||||
|
||||
for ( const [ caseEndpoint ] of listingCaseEntries ) {
|
||||
t.context.listingsDetails[ caseEndpoint ] = new ListingDetails( t.context.listings[ caseEndpoint ] )
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function parseHTML ( htmlString ) {
|
||||
const dom = new JSDOM( htmlString )
|
||||
|
||||
return {
|
||||
dom,
|
||||
window: dom.window,
|
||||
document: dom.window.document
|
||||
}
|
||||
}
|
||||
|
||||
test( 'Listings have valid api endpoints', async t => {
|
||||
const { listingsDetails } = t.context
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingCaseEntries ) {
|
||||
|
||||
const apiPath = listingsDetails[ caseEndpoint ].apiEndpointPath
|
||||
|
||||
t.assert( listingCase.apiEndpointPath === apiPath, `${ caseEndpoint } has a valid api endpoint` )
|
||||
}
|
||||
})
|
||||
|
||||
test( 'Listings with videos have preload data for initialVideo', async t => {
|
||||
const { listingsDetails } = t.context
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingCaseEntries ) {
|
||||
|
||||
const listingDetails = listingsDetails[ caseEndpoint ]
|
||||
|
||||
t.assert( listingDetails.hasInitialVideo === listingCase.expectInitialVideo, `${ caseEndpoint } has initial video` )
|
||||
|
||||
// Stop here if we don't have an initial video
|
||||
if ( !listingDetails.hasInitialVideo ) continue
|
||||
|
||||
// t.log('listingDetails.initialVideo', listingDetails.initialVideo)
|
||||
|
||||
// Get headProperties for image preloading
|
||||
const preloadHeadChecks = headPropertyTypes[ 'link[rel="preload"]' ]
|
||||
|
||||
const images = getVideoImages( listingDetails.initialVideo )
|
||||
|
||||
// Check if the head object properties are correct
|
||||
for ( const preload of images.preloads ) {
|
||||
for ( const [ propertyName, checkMethod ] of Object.entries( preloadHeadChecks ) ) {
|
||||
// Skip count property
|
||||
if ( propertyName === 'count' ) continue
|
||||
|
||||
const value = preload[ propertyName ]
|
||||
|
||||
t.assert( checkMethod( value ), `${ propertyName } failed. Value is '${ value }' for '${ images.imgSrc }'` )
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test('Listings have valid headings', async t => {
|
||||
const { listingsDetails } = t.context
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingCaseEntries ) {
|
||||
|
||||
|
||||
// Build listing details
|
||||
const listingDetails = listingsDetails[ caseEndpoint ]
|
||||
const listingPageHead = new PageHead( listingDetails.headOptions )
|
||||
|
||||
// console.log( 'pageMeta', listingPageHead.metaMarkup )
|
||||
|
||||
// Parse into dom
|
||||
// so we can get data via selectors
|
||||
const { document } = parseHTML( listingPageHead.metaAndLinkMarkup )
|
||||
|
||||
for ( const [ selector, checks ] of Object.entries( headPropertyTypes ) ) {
|
||||
const elements = document.querySelectorAll( selector )
|
||||
|
||||
let count = 1
|
||||
|
||||
if ( has( checks, 'count' ) ) {
|
||||
count = checks.count
|
||||
// delete checks.count
|
||||
}
|
||||
|
||||
if ( count !== false ) {
|
||||
// Fail if there's more or less than one element
|
||||
t.is( elements.length, count, `${ selector } count is ${ elements.length } but should be ${ count }` )
|
||||
}
|
||||
|
||||
for( const element of elements ) {
|
||||
for ( const [ check, checkMethod ] of Object.entries( checks ) ) {
|
||||
// console.log( `Ckecking ${ selector } ${ check }` )
|
||||
|
||||
const value = element.getAttribute( check )
|
||||
|
||||
t.assert( checkMethod( value ), `${ check } on ${ selector } failed. Value is '${ value }'` )
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test( 'Listings with videos have structured data', async t => {
|
||||
const { listingsDetails } = t.context
|
||||
|
||||
for ( const [ caseEndpoint, listingCase ] of listingCaseEntries ) {
|
||||
|
||||
const listingDetails = listingsDetails[ caseEndpoint ]
|
||||
const listingPageHead = new PageHead( {
|
||||
...listingDetails.headOptions,
|
||||
pathname: caseEndpoint
|
||||
})
|
||||
|
||||
// Stop here if we're not expecting Video Structured Data
|
||||
if ( !listingCase.shouldHaveVideoStucturedData ) {
|
||||
|
||||
// Check that the non-video listing doesn't have video structured data
|
||||
t.assert( !listingPageHead.structuredDataMarkup.includes('VideoObject'), `${ caseEndpoint } has video structured data` )
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
|
||||
// t.log('listingDetails.initialVideo', listingDetails.initialVideo)
|
||||
// t.log( 'caseEndpoint', caseEndpoint )
|
||||
|
||||
// Assert that the structured data is not empty
|
||||
t.assert( listingPageHead.structuredDataMarkup.trim() !== '', `${ caseEndpoint } has structured data` )
|
||||
|
||||
// https://github.com/glitchdigital/structured-data-testing-tool#api
|
||||
const testResult = await structuredDataTestHtml( listingPageHead.allHeadMarkup , {
|
||||
presets: [ Google ],
|
||||
schemas: [ 'VideoObject' ]
|
||||
}).then(res => {
|
||||
return res
|
||||
}).catch(err => {
|
||||
// console.warn( 'Structured Data error', err.error )
|
||||
|
||||
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
|
||||
validationError.errors = Array.from( err.res.failed ).map( fail => fail.error )
|
||||
|
||||
throw validationError
|
||||
|
||||
// return
|
||||
}
|
||||
|
||||
throw new Error( 'Structured data testing error.', err.error )
|
||||
})
|
||||
|
||||
// t.log( 'testResult', testResult )
|
||||
|
||||
// Assert that no Structured Data tests failed
|
||||
t.assert( testResult.failed.length === 0, `${ caseEndpoint } has valid structured data` )
|
||||
}
|
||||
})
|
||||
216
test/_disabled/main.js
Normal file
216
test/_disabled/main.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
// 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()
|
||||
|
||||
// })
|
||||
64
test/_disabled/prebuild/filters.js
Normal file
64
test/_disabled/prebuild/filters.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import test from 'ava'
|
||||
// import MarkdownIt from 'markdown-it'
|
||||
|
||||
// import { isValidHttpUrl } from '~/helpers/check-types.js'
|
||||
import { StorkFilters } from '~/helpers/stork/browser.js'
|
||||
|
||||
|
||||
test('Can Toggle off existing filter' , async t => {
|
||||
const filters = new StorkFilters({
|
||||
initialFilters: {
|
||||
'test': 'yes'
|
||||
}
|
||||
})
|
||||
|
||||
filters.toggleFilter('test')
|
||||
|
||||
t.deepEqual(filters.has('test'), false)
|
||||
|
||||
filters.toggleFilter('test', 'yes')
|
||||
filters.toggleFilter('status', 'native')
|
||||
|
||||
t.deepEqual(filters.has('test'), true, 'Has test filter')
|
||||
|
||||
t.deepEqual(filters.asQuery, 'test_yes status_native', 'Has correct filters for query')
|
||||
|
||||
filters.toggleFilter('status_native')
|
||||
|
||||
t.deepEqual(filters.asQuery, 'test_yes', 'Has only test filter')
|
||||
})
|
||||
|
||||
|
||||
test('Can handle query values with multiple underscores', async t => {
|
||||
const filters = new StorkFilters({
|
||||
initialFilters: {
|
||||
'test': 'value_with_multiple_underscores'
|
||||
}
|
||||
})
|
||||
|
||||
t.log( 'filters.asQuery', filters.asQuery )
|
||||
|
||||
t.assert( filters.has( 'test_value_with_multiple_underscores' ) , 'Has correct filters for query' )
|
||||
})
|
||||
|
||||
|
||||
test( 'Can update existing filter', async t => {
|
||||
const filters = new StorkFilters({
|
||||
initialFilters: {
|
||||
'test': 'works_no'
|
||||
}
|
||||
})
|
||||
|
||||
filters.toggleFilter('test_works_yes')
|
||||
|
||||
t.deepEqual( filters.asQuery, 'test_works_yes', 'Has updated filter')
|
||||
})
|
||||
|
||||
|
||||
test( 'Can set filters from string', async t => {
|
||||
const filters = new StorkFilters()
|
||||
|
||||
filters.setFromString( 'test_works_yes' )
|
||||
|
||||
t.deepEqual( filters.asQuery, 'test_works_yes', 'Has updated filter')
|
||||
})
|
||||
238
test/_disabled/prebuild/index.js
Normal file
238
test/_disabled/prebuild/index.js
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import fs from 'fs-extra'
|
||||
import test from 'ava'
|
||||
// import MarkdownIt from 'markdown-it'
|
||||
|
||||
import { isValidHttpUrl } from '~/helpers/check-types.js'
|
||||
import { buildReadmeAppList } from '~/helpers/build-app-list.js'
|
||||
import {
|
||||
matchesWholeWord,
|
||||
fuzzyMatchesWholeWord,
|
||||
eitherMatches
|
||||
} from '~/helpers/matching.js'
|
||||
import {
|
||||
PaginatedList
|
||||
} from '~/helpers/api/pagination.js'
|
||||
|
||||
|
||||
require('dotenv').config()
|
||||
|
||||
const allowedTitleCharacters = new Set( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 -+:_.®/\()音乐体验版'.split('') )
|
||||
|
||||
// Detect Emojis(Extended Pictograph) in string
|
||||
// https://stackoverflow.com/a/64007175/1397641
|
||||
function hasEmoji ( string ) {
|
||||
return /\p{Extended_Pictographic}/u.test( string )
|
||||
}
|
||||
|
||||
test.before(async t => {
|
||||
const readmeFileContent = await fs.readFile('./README.md', 'utf-8')
|
||||
|
||||
|
||||
// Store sitemap urls to context
|
||||
t.context.readmeFileContent = readmeFileContent
|
||||
t.context.readmeAppList = buildReadmeAppList({
|
||||
readmeContent: t.context.readmeFileContent,
|
||||
scanListMap: new Map(),
|
||||
commits: []
|
||||
})
|
||||
})
|
||||
|
||||
test('README Apps are formated correctly', (t) => {
|
||||
// console.log('t.context.sitemapUrls', t.context.sitemapUrls)
|
||||
|
||||
const {
|
||||
readmeAppList
|
||||
} = t.context
|
||||
|
||||
// Store found apps so we can check for duplicates
|
||||
const foundApps = new Set()
|
||||
|
||||
// Store found invalid apps so we can count and report them
|
||||
const invalidApps = new Set()
|
||||
|
||||
|
||||
for (const readmeApp of readmeAppList) {
|
||||
const cleanedAppName = readmeApp.name//.toLowerCase()
|
||||
|
||||
// Check that app has not already been found
|
||||
if (foundApps.has(cleanedAppName)) {
|
||||
t.fail(`Duplicate app found: ${readmeApp.name}`)
|
||||
invalidApps.add(cleanedAppName)
|
||||
}
|
||||
// Store this app so we can check for future duplicates
|
||||
foundApps.add(cleanedAppName)
|
||||
|
||||
// Check that all related links urls are valid
|
||||
for (const relatedLink of readmeApp.relatedLinks) {
|
||||
if ( !isValidHttpUrl( relatedLink.href ) ) {
|
||||
t.log('relatedLink.href', readmeApp.name, relatedLink.href)
|
||||
|
||||
t.fail(`README App ${readmeApp.name} does not have valid url`, readmeApp.url)
|
||||
invalidApps.add(cleanedAppName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check that status text is free of markdown
|
||||
if ( readmeApp.text.includes('](') ) {
|
||||
t.fail(`README App ${readmeApp.name} markdown in status text`)
|
||||
invalidApps.add(cleanedAppName)
|
||||
}
|
||||
|
||||
// Check that status has an emoji
|
||||
if ( hasEmoji( readmeApp.text ) === false ) {
|
||||
t.fail(`README App ${readmeApp.name} does not have emoji`)
|
||||
invalidApps.add(cleanedAppName)
|
||||
}
|
||||
|
||||
// Check for not allowed characters in app name
|
||||
for ( const character of cleanedAppName ) {
|
||||
if ( !allowedTitleCharacters.has( character ) ) {
|
||||
|
||||
// badCharacter = readmeApp.name[firstBadCharacterIndex]
|
||||
|
||||
// t.log( readmeApp )
|
||||
t.fail(`README App Title ${readmeApp.name} has non-alphanumeric character ${character}(charCode ${character.charCodeAt(0)})`)
|
||||
invalidApps.add(cleanedAppName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
t.log( readmeAppList.length - invalidApps.size, 'valid apps found' )
|
||||
t.log( readmeAppList.length, 'apps found in README' )
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
|
||||
function sortAppsAlphabetically ( a, b ) {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
||||
|
||||
test('README Apps are in alphbetical order', (t) => {
|
||||
|
||||
const {
|
||||
readmeAppList
|
||||
} = t.context
|
||||
|
||||
const appsByCategory = new Map()
|
||||
|
||||
|
||||
|
||||
// Group apps by category
|
||||
for ( const readmeApp of readmeAppList ) {
|
||||
const category = readmeApp.category.slug
|
||||
|
||||
if ( !appsByCategory.has(category) ) {
|
||||
appsByCategory.set(category, [])
|
||||
}
|
||||
|
||||
appsByCategory.get( category ).push(readmeApp)
|
||||
}
|
||||
|
||||
// Sort apps in groups alphabetically
|
||||
for ( const [ category, apps ] of appsByCategory ) {
|
||||
|
||||
const unsortedApps = apps.slice()
|
||||
|
||||
// Sort apps in category in place
|
||||
apps.sort(sortAppsAlphabetically)
|
||||
|
||||
// Check sorted sorted apps against unsorted apps
|
||||
for ( const [ index, unsortedApp ] of unsortedApps.entries() ) {
|
||||
const sortedApp = apps[index]
|
||||
|
||||
if ( sortedApp.slug !== unsortedApp.slug ) {
|
||||
t.fail(`README App at index ${index} of ${category} is ${unsortedApp.name} but should be ${sortedApp.name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
|
||||
|
||||
const namesWithPlusses = [
|
||||
'Xournal++',
|
||||
'Notepad++'
|
||||
]
|
||||
|
||||
test('Can match names with pluses', (t) => {
|
||||
|
||||
|
||||
|
||||
// Sort apps in groups alphabetically
|
||||
for ( const nameWithPluses of namesWithPlusses ) {
|
||||
|
||||
const haystack = `FDKLS:KF ${nameWithPluses}NDFLSKFLSJDK`
|
||||
|
||||
t.assert( matchesWholeWord( nameWithPluses, haystack ) )
|
||||
|
||||
t.assert( fuzzyMatchesWholeWord( nameWithPluses, haystack ) )
|
||||
|
||||
t.assert( eitherMatches( nameWithPluses, haystack ) )
|
||||
t.assert( eitherMatches( haystack, nameWithPluses ) )
|
||||
}
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
|
||||
test('Can paginate', async (t) => {
|
||||
const cases = [
|
||||
{
|
||||
from: {
|
||||
list: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
|
||||
perPage: 2
|
||||
},
|
||||
expect: {
|
||||
pageCount: 5,
|
||||
pages: [
|
||||
{
|
||||
number: 1,
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: false,
|
||||
items: [1, 2],
|
||||
json: '[1,2]'
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: true,
|
||||
items: [3, 4],
|
||||
json: '[3,4]'
|
||||
},
|
||||
|
||||
// Last page should have less than perPage items
|
||||
{
|
||||
number: 5,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: true,
|
||||
items: [9],
|
||||
json: '[9]'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
for ( const { from, expect } of cases ) {
|
||||
|
||||
const paginatedList = new PaginatedList( from )
|
||||
|
||||
// Assert that page count is correct
|
||||
t.is( paginatedList.pageCount, expect.pageCount, 'pageCount is incorrect' )
|
||||
|
||||
// Assert that the pages we're expecting are there
|
||||
for ( const expectedPage of expect.pages ) {
|
||||
// Get respective output page
|
||||
const outputPage = paginatedList.pages[ expectedPage.number - 1 ]
|
||||
|
||||
t.deepEqual( outputPage, expectedPage, `Page ${ expectedPage.number } is an unexpected structure` )
|
||||
}
|
||||
}
|
||||
|
||||
} )
|
||||
145
test/_disabled/scanner/client.test.mjs
Normal file
145
test/_disabled/scanner/client.test.mjs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect
|
||||
} from 'vitest'
|
||||
// Vitest Web Worker Compatibility - https://github.com/vitest-dev/vitest/tree/main/packages/web-worker
|
||||
import '@vitest/web-worker'
|
||||
|
||||
|
||||
import fs from 'fs-extra'
|
||||
import path from 'node:path'
|
||||
// https://github.com/mrmlnc/fast-glob
|
||||
import glob from 'fast-glob'
|
||||
import { LocalFileData } from 'get-file-object-from-local-path'
|
||||
import { Zip } from 'zip-lib'
|
||||
|
||||
import { runScanWorker } from '~/helpers/scanner/client.mjs'
|
||||
|
||||
|
||||
const appGlobOptions = {
|
||||
onlyFiles: false,
|
||||
deep: 1
|
||||
}
|
||||
|
||||
const appsPath = 'test/_artifacts/apps'
|
||||
|
||||
const tempPath = 'test/_artifacts/temp'
|
||||
|
||||
// TODO: Unsupported Apps:
|
||||
// Alt Tab 6.29.0 - Hangs
|
||||
// AVTouchBar 3.0.6 - Times out
|
||||
// Silicon - Fail with both MachoNode and MachoManiac
|
||||
// arm_idafree76_mac 7.6 - Hangs
|
||||
// Batteries 2.2.4 - Hangs
|
||||
// BetterZip 5.1.1 - Hangs
|
||||
// BlueJeans - May work but doesn't work with zip compression
|
||||
// Coreform-Cubit - No info.plist, may not need to be supported
|
||||
const plainAppBundles = glob
|
||||
.sync( `${ appsPath }/**/*.app`, appGlobOptions )
|
||||
.filter( bundlePath => {
|
||||
return true
|
||||
// return bundlePath.includes( 'AV' )
|
||||
})
|
||||
|
||||
|
||||
async function makeZipFromBundlePath ( bundlePath ) {
|
||||
const archivePath = `${ tempPath }/${ bundlePath.split('/').pop() }.zip`
|
||||
|
||||
// Delete any existing archive
|
||||
if ( await fs.exists( archivePath ) ) {
|
||||
console.log( 'Deleting existing archive', archivePath )
|
||||
await fs.unlink( archivePath )
|
||||
}
|
||||
|
||||
// console.log( 'archivePath', archivePath )
|
||||
|
||||
// await compress( bundlePath, archivePath )
|
||||
|
||||
const zipLib = new Zip()
|
||||
|
||||
// Adds a folder from the file system, putting its contents at the root of archive
|
||||
zipLib.addFolder( bundlePath )
|
||||
|
||||
// Generate zip file.
|
||||
await zipLib.archive( archivePath )
|
||||
|
||||
// Create a File Object from the zip file
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/File/File
|
||||
const archiveFile = new LocalFileData( archivePath )
|
||||
|
||||
// console.log( 'archiveFile', archiveFile )
|
||||
|
||||
return archiveFile
|
||||
}
|
||||
|
||||
describe.concurrent('Apps', async () => {
|
||||
|
||||
// Compress plain app bundles to zipped File Objects
|
||||
for ( const bundlePath of plainAppBundles ) {
|
||||
|
||||
// Get the App's file name from bundlePath
|
||||
const appName = path.basename( bundlePath )
|
||||
|
||||
// Generate a faux JavaScript File instance
|
||||
const file = await makeZipFromBundlePath( bundlePath )
|
||||
|
||||
// Scan the app
|
||||
const { scan } = await runScanWorker( file, ( details ) => {
|
||||
console.log( 'New message from runScanWorker:', details )
|
||||
} )
|
||||
|
||||
|
||||
it( `Can read info.plist for ${ appName } bundle` , () => {
|
||||
// console.log( 'infoPlist', scan.infoPlist )
|
||||
|
||||
expect( scan.hasInfoPlist ).toBe( true )
|
||||
})
|
||||
|
||||
it( `Can read macho meta for entry ${ appName } bundle`, () => {
|
||||
// console.log( 'machoMeta', scan.machoMeta )
|
||||
|
||||
expect( scan.hasMachoMeta ).toBe( true )
|
||||
})
|
||||
|
||||
it( `Can provide scan info for ${ appName } bundle`, () => {
|
||||
// console.log( 'machoMeta', scan.machoMeta )
|
||||
|
||||
expect( scan.hasInfo ).toBe( true )
|
||||
|
||||
// Expect the scan info to have a bundle name that matches the app name
|
||||
expect( scan.info.filename ).toContain( appName )
|
||||
|
||||
// Expect app version is string
|
||||
expect( typeof scan.info.appVersion ).toBe( 'string' )
|
||||
|
||||
// Expect that machoMeta is an object
|
||||
expect( typeof scan.info.machoMeta ).toBe( 'object' )
|
||||
|
||||
// Expect that supportedArchitectures is not empty
|
||||
expect( scan.supportedArchitectures.length ).toBeGreaterThan( 0 )
|
||||
|
||||
// Expect that processorType is a string
|
||||
expect( typeof scan.supportedArchitectures[0].processorType ).toBe( 'string' )
|
||||
|
||||
const validCPUTypes = [
|
||||
'ANY', 'VAX', 'MC680', 'X86', 'MIPS', 'MC98000', 'HPPA', 'ARM', 'ARM64', 'MC88000', 'SPARC', 'I860', 'POWERPC', 'POWERPC64'
|
||||
]
|
||||
|
||||
// Expect that processorType is a valid CPU type
|
||||
expect( validCPUTypes.includes( scan.supportedArchitectures[0].processorType ) ).toBe( true )
|
||||
|
||||
// Expect that first of machoMeta.architectures has processorSubType as string
|
||||
// expect( typeof scan.supportedArchitectures[0].processorSubType ).toBeTruthy()
|
||||
|
||||
// Export info.infoPlist to be none empty object
|
||||
expect( typeof scan.info.infoPlist ).toBe( 'object' )
|
||||
expect( Object.keys( scan.info.infoPlist ).length ).toBeGreaterThan( 0 )
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue