UNPKG

electron-apps

Version:

A collection of apps built on Electron

257 lines (222 loc) • 9.18 kB
const categories = require('../lib/app-categories') const mocha = require('mocha') const describe = mocha.describe const it = mocha.it const fs = require('fs') const path = require('path') const expect = require('chai').expect const yaml = require('js-yaml') const isUrl = require('is-url') const { URL } = require('url') const cleanDeep = require('clean-deep') const imageSize = require('image-size') const makeColorAccessible = require('make-color-accessible') const slugg = require('slugg') const grandfatheredDescriptions = require('../lib/grandfathered-descriptions') const grandfatheredLinks = require('../lib/grandfathered-links.js') const grandfatheredSlugs = require('../lib/grandfathered-small-icons') const slugs = fs .readdirSync(path.join(__dirname, '../apps')) .filter((filename) => { return fs .statSync(path.join(__dirname, `../apps/${filename}`)) .isDirectory() }) describe('human-submitted app data', () => { it('includes lots of apps', () => { expect(slugs.length).to.be.above(200) }) slugs.forEach((slug) => { describe(slug, () => { const basedir = path.join(__dirname, `../apps/${slug}`) const yamlFile = `${slug}.yml` const yamlPath = path.join(basedir, yamlFile) const iconPath = path.join(basedir, `${slug}-icon.png`) it('is in a directory whose name is lowercase with dashes as a delimiter', () => { expect(slugg(slug)).to.equal(slug) }) it(`includes a data file named ${slug}.yml`, () => { expect(fs.existsSync(yamlPath)).to.equal(true) }) describe(`${yamlFile}`, () => { const app = yaml.load(fs.readFileSync(yamlPath)) it('has a name', () => { expect(app.name.length).to.be.above(0) }) describe('description', () => { it('exists', () => { expect(app.description.length).to.be.above(0) }) it('should not start with app name', () => { const appName = app.name.toLowerCase() const description = app.description.toLowerCase() expect(description).to.satisfy((desc) => !desc.startsWith(appName)) }) const descIsGrandfathered = grandfatheredDescriptions.includes(slug) if (!descIsGrandfathered) { it('should start with a capital letter', () => { const firstLetter = app.description[0] expect(firstLetter).to.equal(firstLetter.toUpperCase()) }) it('should end with a period / full stop', () => { expect(app.description[app.description.length - 1]).to.equal( '.', `Description should end in a period / full stop: '${app.description}'` ) }) it('should not mention Electron since Electron is already implied', () => { const description = app.description.toLowerCase() expect(description.indexOf('electron')).to.equal( -1, `Description should not mention Electron, as Electron is already implied: ${description}` ) }) it('should not start description with "A" or "An"', () => { const descriptionFirstWord = app.description .toLowerCase() .split(' ', 1)[0] const badStarts = ['a', 'an'] expect(badStarts).to.not.include( descriptionFirstWord, `Description should not start with 'A' or 'An': '${app.description}'` ) }) } }) const linksAreGrandfathered = grandfatheredLinks.includes(slug) if (!linksAreGrandfathered) { // walk an object subtree looking for URLs const getObjectUrls = (root) => { const found = [] const queue = [root] while (queue.length !== 0) { const vals = Object.values(queue.shift()) found.push(...vals.filter(isUrl).map((v) => new URL(v))) queue.push(...vals.filter((v) => typeof v === 'object')) } return found } it('should use ssl links', () => { const goodProtocols = ['https:', 'sftp:'] const urls = getObjectUrls(app) urls.forEach((url) => expect(url.protocol, url).to.be.oneOf(goodProtocols) ) }) } it('has a website with a valid URL (or no website)', () => { expect(!app.website || isUrl(app.website)).to.equal(true) }) it('has a valid repository URL (or no repository)', () => { expect(!app.repository || isUrl(app.repository)).to.equal(true) }) describe('keywords', () => { it('should, if present, be an array of keywords', () => { expect(app.keywords || []).to.be.an('array') }) it("should not include 'electron'", () => { expect( (app.keywords || []).map((key) => key.toLocaleLowerCase()) ).to.not.include('electron') }) it('should not include duplicates', () => { const keywords = app.keywords || [] expect(keywords.sort().toString()).to.equal( [...new Set(keywords).values()].sort().toString() ) }) }) it('has a valid category', () => { expect(app.category.length).to.be.above(0) expect(app.category).to.be.oneOf(categories) }) describe('colors', () => { it(`allows goodColorOnWhite to be set, but it must be accessible`, () => { // accessible: contrast ratio of 4.5:1 or greater (white background) const color = app.goodColorOnWhite if (color) { const accessibleColor = makeColorAccessible(color) expect(color === accessibleColor).to.equal( true, `${slug}: contrast ratio too low for goodColorOnWhite. Try: ${accessibleColor}` ) } }) it(`allows goodColorOnBlack to be set, but it must be accessible`, () => { // accessible: contrast ratio of 4.5:1 or greater (black background) const color = app.goodColorOnBlack if (color) { const accessibleColor = makeColorAccessible(color, { background: 'black', }) expect(color === accessibleColor).to.equal( true, `${slug}: contrast ratio too low for goodColorOnBlack. Try: ${accessibleColor}` ) } }) it(`allows faintColorOnWhite to be set`, () => { const color = app.faintColorOnWhite if (color) { expect(color).to.match( /rgba\(\d+, \d+, \d+, /, `${slug}'s faintColorOnWhite must be an rgba string` ) } }) }) it('has no empty properties', () => { expect(cleanDeep(app)).to.deep.equal(app) }) describe('screenshots', () => { const screenshots = app.screenshots || [] it('requires imageUrl to be a fully-qualified HTTPS URL', () => { screenshots.forEach((screenshot) => { expect( isUrl(screenshot.imageUrl) && /^https/.test(screenshot.imageUrl) ).to.equal( true, `${app.slug} screenshot imageUrl must be a fully-qualified HTTPS URL` ) }) }) it('requires linkUrl to be a fully-qualified URL, if present', () => { screenshots.forEach((screenshot) => { expect(!screenshot.linkUrl || isUrl(screenshot.linkUrl)).to.equal( true, `${app.slug} screenshot linkURL must be a fully qualified URL` ) }) }) }) it('has a valid YouTube URL (or none)', () => { expect( !app.youtube_video_url || isUrl(app.youtube_video_url) ).to.equal(true) }) }) describe('icon', () => { it(`exists as ${slug}-icon.png`, () => { expect(fs.existsSync(iconPath)).to.equal( true, `${slug}-icon.png not found` ) }) it('is a square', function () { if (!fs.existsSync(iconPath)) return this.skip() const dimensions = imageSize(iconPath) expect(dimensions.width).to.be.a('number') expect(dimensions.width).to.equal(dimensions.height) }) const minPixels = grandfatheredSlugs.indexOf(slug) > -1 ? 128 : 256 const maxPixels = 1024 it(`is between ${minPixels}px x ${minPixels}px and ${maxPixels}px x ${maxPixels}px`, function () { if (!fs.existsSync(iconPath)) return this.skip() const dimensions = imageSize(iconPath) expect(dimensions.width).to.be.within(minPixels, maxPixels) expect(dimensions.height).to.be.within(minPixels, maxPixels) }) }) }) }) })