diff --git a/source/.eslintrc.yml b/source/.eslintrc.yml index 07fb0281..6d23f3da 100644 --- a/source/.eslintrc.yml +++ b/source/.eslintrc.yml @@ -126,7 +126,9 @@ rules: func-call-spacing: error arrow-spacing: error generator-star-spacing: error + object-curly-spacing: [error, never] rest-spread-spacing: error + key-spacing: [error, {afterColon: false}] computed-property-spacing: error array-bracket-spacing: [error, never] no-whitespace-before-property: error diff --git a/tests/ci.test.js b/tests/ci.test.js index 6d48f458..22208f31 100644 --- a/tests/ci.test.js +++ b/tests/ci.test.js @@ -1,20 +1,22 @@ //Imports - const path = require("path") - const git = require("simple-git")(path.join(__dirname, "..")) +const path = require("path") +const git = require("simple-git")(path.join(__dirname, "..")) //Edited files list - const diff = async () => (await git.diff("origin/master...", ["--name-status"])).split("\n").map(x => x.trim()).filter(x => /^M\s+/.test(x)).map(x => x.replace(/^M\s+/, "")) +const diff = async () => (await git.diff("origin/master...", ["--name-status"])).split("\n").map(x => x.trim()).filter(x => /^M\s+/.test(x)).map(x => x.replace(/^M\s+/, "")) //Files editions - describe("Check files editions (checkout your files if needed)", () => { - describe("Auto-generated files were not modified", () => void test.each([ +describe("Check files editions (checkout your files if needed)", () => { + describe("Auto-generated files were not modified", () => + void test.each([ "README.md", "source/plugins/README.md", "source/templates/README.md", "action.yml", - "settings.example.json" + "settings.example.json", ])("%s", async file => expect((await diff()).includes(file)).toBe(false))) - describe("Repository level files were not modified", () => void test.each([ + describe("Repository level files were not modified", () => + void test.each([ ".github/config/*", ".github/ISSUE_TEMPLATE/*", ".github/PULL_REQUEST_TEMPLATE/*", @@ -35,11 +37,12 @@ "tests/ci.test.js", "source/.eslintrc.yml", "source/app/mocks/.eslintrc.yml", - "vercel.json" + "vercel.json", ])("%s", async file => expect((await diff()).filter(edited => new RegExp(`^${file.replace(/[.]/g, "[.]").replace(/[*]/g, "[\\s\\S]*")}$`).test(edited)).length).toBe(0))) - }) +}) //Templates editions - describe("Check templates editions", () => { - test("Use community templates instead (see https://github.com/lowlighter/metrics/tree/master/source/templates/community)", async () => void expect((await diff()).filter(edited => /^sources[/]templates[/]/.test(edited) && /^source[/]templates[/](?:classic|terminal|markdown|repository|community)[/][\s\S]*$/.test(edited)).length).toBe(0)) - }) +describe("Check templates editions", () => { + test("Use community templates instead (see https://github.com/lowlighter/metrics/tree/master/source/templates/community)", async () => + void expect((await diff()).filter(edited => /^sources[/]templates[/]/.test(edited) && /^source[/]templates[/](?:classic|terminal|markdown|repository|community)[/][\s\S]*$/.test(edited)).length).toBe(0)) +}) diff --git a/tests/metrics.test.js b/tests/metrics.test.js index 6d4c3198..e9bdb263 100644 --- a/tests/metrics.test.js +++ b/tests/metrics.test.js @@ -1,21 +1,22 @@ //Imports - const processes = require("child_process") - const yaml = require("js-yaml") - const fs = require("fs") - const path = require("path") - const url = require("url") - const axios = require("axios") - const faker = require("faker") - const ejs = require("ejs") +const processes = require("child_process") +const yaml = require("js-yaml") +const fs = require("fs") +const path = require("path") +const url = require("url") +const axios = require("axios") +const faker = require("faker") +const ejs = require("ejs") //Github action - const action = yaml.load(fs.readFileSync(path.join(__dirname, "../action.yml"), "utf8")) - action.defaults = Object.fromEntries(Object.entries(action.inputs).map(([key, {default:value}]) => [key, value])) - action.input = vars => Object.fromEntries([...Object.entries(action.defaults), ...Object.entries(vars)].map(([key, value]) => [`INPUT_${key.toLocaleUpperCase()}`, value])) - action.run = async (vars) => await new Promise((solve, reject) => { +const action = yaml.load(fs.readFileSync(path.join(__dirname, "../action.yml"), "utf8")) +action.defaults = Object.fromEntries(Object.entries(action.inputs).map(([key, { default: value }]) => [key, value])) +action.input = vars => Object.fromEntries([...Object.entries(action.defaults), ...Object.entries(vars)].map(([key, value]) => [`INPUT_${key.toLocaleUpperCase()}`, value])) +action.run = async vars => + await new Promise((solve, reject) => { let [stdout, stderr] = ["", ""] - const env = {...process.env, ...action.input(vars), GITHUB_REPOSITORY:"lowlighter/metrics"} - const child = processes.spawn("node", ["source/app/action/index.mjs"], {env}) + const env = { ...process.env, ...action.input(vars), GITHUB_REPOSITORY: "lowlighter/metrics" } + const child = processes.spawn("node", ["source/app/action/index.mjs"], { env }) child.stdout.on("data", data => stdout += data) child.stderr.on("data", data => stderr += data) child.on("close", code => { @@ -27,111 +28,124 @@ }) //Web instance - const web = {} - web.run = async (vars) => (await axios(`http://localhost:3000/lowlighter?${new url.URLSearchParams(Object.fromEntries(Object.entries(vars).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value])))}`)).status === 200 - web.start = async () => new Promise(solve => { +const web = {} +web.run = async vars => (await axios(`http://localhost:3000/lowlighter?${new url.URLSearchParams(Object.fromEntries(Object.entries(vars).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value])))}`)).status === 200 +web.start = async () => + new Promise(solve => { let stdout = "" - web.instance = processes.spawn("node", ["source/app/web/index.mjs"], {env:{...process.env, USE_MOCKED_DATA:true, NO_SETTINGS:true}}) + web.instance = processes.spawn("node", ["source/app/web/index.mjs"], { env: { ...process.env, USE_MOCKED_DATA: true, NO_SETTINGS: true } }) web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null)) web.instance.stderr.on("data", data => console.error(`${data}`)) }) - web.stop = async () => await web.instance.kill("SIGKILL") +web.stop = async () => await web.instance.kill("SIGKILL") //Web instance placeholder - require("./../source/app/web/statics/app.placeholder.js") - const placeholder = globalThis.placeholder - delete globalThis.placeholder - placeholder.init({faker, ejs, axios:{async get(url) { return axios(`http://localhost:3000${url}`) }}}) - placeholder.run = async (vars) => { - const options = Object.fromEntries(Object.entries(vars).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value])) - const enabled = Object.fromEntries(Object.entries(vars).filter(([key]) => /^plugin_[a-z]+$/.test(key))) - const config = Object.fromEntries(Object.entries(options).filter(([key]) => /^config[.]/.test(key))) - const base = Object.fromEntries(Object.entries(options).filter(([key]) => /^base[.]/.test(key))) - return typeof await placeholder({ - templates:{selected:vars.template}, - plugins:{enabled:{...enabled, base}, options}, - config, - version:"TEST", - user:"lowlighter", - avatar:"https://github.com/lowlighter.png", - }) === "string" - } +require("./../source/app/web/statics/app.placeholder.js") +const placeholder = globalThis.placeholder +delete globalThis.placeholder +placeholder.init({ + faker, + ejs, + axios: { + async get(url) { + return axios(`http://localhost:3000${url}`) + }, + }, +}) +placeholder.run = async vars => { + const options = Object.fromEntries(Object.entries(vars).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value])) + const enabled = Object.fromEntries(Object.entries(vars).filter(([key]) => /^plugin_[a-z]+$/.test(key))) + const config = Object.fromEntries(Object.entries(options).filter(([key]) => /^config[.]/.test(key))) + const base = Object.fromEntries(Object.entries(options).filter(([key]) => /^base[.]/.test(key))) + return typeof await placeholder({ + templates: { selected: vars.template }, + plugins: { enabled: { ...enabled, base }, options }, + config, + version: "TEST", + user: "lowlighter", + avatar: "https://github.com/lowlighter.png", + }) === "string" +} //Setup - beforeAll(async done => { - //Clean community template - await fs.promises.rmdir(path.join(__dirname, "../source/templates/@classic"), {recursive:true}) - //Start web instance - await web.start() - done() - }) +beforeAll(async done => { + //Clean community template + await fs.promises.rmdir(path.join(__dirname, "../source/templates/@classic"), { recursive: true }) + //Start web instance + await web.start() + done() +}) //Teardown - afterAll(async done => { - //Stop web instance - await web.stop() - //Clean community template - await fs.promises.rmdir(path.join(__dirname, "../source/templates/@classic"), {recursive:true}) - done() - }) +afterAll(async done => { + //Stop web instance + await web.stop() + //Clean community template + await fs.promises.rmdir(path.join(__dirname, "../source/templates/@classic"), { recursive: true }) + done() +}) //Load metadata (as jest doesn't support ESM modules, we use this dirty hack) - const metadata = JSON.parse(`${processes.spawnSync("node", [ - "--input-type", "module", - "--eval", 'import metadata from "./source/app/metrics/metadata.mjs";console.log(JSON.stringify(await metadata({log:false})))' - ]).stdout}`) +const metadata = JSON.parse(`${ + processes.spawnSync("node", [ + "--input-type", + "module", + "--eval", + 'import metadata from "./source/app/metrics/metadata.mjs";console.log(JSON.stringify(await metadata({log:false})))', + ]).stdout +}`) //Build tests index - const tests = [] - for (const name in metadata.plugins) { - const cases = yaml - .load(fs.readFileSync(path.join(__dirname, "../source/plugins", name, "tests.yml"), "utf8")) - .map(({name:test, with:inputs, modes = [], timeout}) => { - const skip = new Set(Object.entries(metadata.templates).filter(([_, {readme:{compatibility}}]) => !compatibility[name]).map(([template]) => template)) - if (!(metadata.plugins[name].supports.includes("repository"))) - skip.add("repository") - return [test, inputs, {skip:[...skip], modes, timeout}] - }) - tests.push(...cases) - } +const tests = [] +for (const name in metadata.plugins) { + const cases = yaml + .load(fs.readFileSync(path.join(__dirname, "../source/plugins", name, "tests.yml"), "utf8")) + .map(({ name: test, with: inputs, modes = [], timeout }) => { + const skip = new Set(Object.entries(metadata.templates).filter(([_, { readme: { compatibility } }]) => !compatibility[name]).map(([template]) => template)) + if (!(metadata.plugins[name].supports.includes("repository"))) + skip.add("repository") + return [test, inputs, { skip: [...skip], modes, timeout }] + }) + tests.push(...cases) +} //Tests run - describe("GitHub Action", () => - describe.each([ - ["classic", {}], - ["terminal", {}], - ["repository", {repo:"metrics"}], - ])("Template : %s", (template, query) => { - for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) - if ((skip.includes(template))||((modes.length)&&(!modes.includes("action")))) - test.skip(name, () => null) - else - test(name, async () => expect(await action.run({template, base:"", query:JSON.stringify(query), plugins_errors_fatal:true, dryrun:true, use_mocked_data:true, verify:true, ...input})).toBe(true), timeout) - }) - ) +describe("GitHub Action", () => + describe.each([ + ["classic", {}], + ["terminal", {}], + ["repository", { repo: "metrics" }], + ])("Template : %s", (template, query) => { + for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) { + if ((skip.includes(template)) || ((modes.length) && (!modes.includes("action")))) + test.skip(name, () => null) + else + test(name, async () => expect(await action.run({ template, base: "", query: JSON.stringify(query), plugins_errors_fatal: true, dryrun: true, use_mocked_data: true, verify: true, ...input })).toBe(true), timeout) + } + })) - describe("Web instance", () => - describe.each([ - ["classic", {}], - ["terminal", {}], - ["repository", {repo:"metrics"}], - ])("Template : %s", (template, query) => { - for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) - if ((skip.includes(template))||((modes.length)&&(!modes.includes("web")))) - test.skip(name, () => null) - else - test(name, async () => expect(await web.run({template, base:0, ...query, plugins_errors_fatal:true, verify:true, ...input})).toBe(true), timeout) - }) - ) +describe("Web instance", () => + describe.each([ + ["classic", {}], + ["terminal", {}], + ["repository", { repo: "metrics" }], + ])("Template : %s", (template, query) => { + for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) { + if ((skip.includes(template)) || ((modes.length) && (!modes.includes("web")))) + test.skip(name, () => null) + else + test(name, async () => expect(await web.run({ template, base: 0, ...query, plugins_errors_fatal: true, verify: true, ...input })).toBe(true), timeout) + } + })) - describe("Web instance (placeholder)", () => - describe.each([ - ["classic", {}], - ["terminal", {}], - ])("Template : %s", (template, query) => { - for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) - if ((skip.includes(template))||((modes.length)&&(!modes.includes("placeholder")))) - test.skip(name, () => null) - else - test(name, async () => expect(await placeholder.run({template, base:0, ...query, ...input})).toBe(true), timeout) - }) - ) +describe("Web instance (placeholder)", () => + describe.each([ + ["classic", {}], + ["terminal", {}], + ])("Template : %s", (template, query) => { + for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) { + if ((skip.includes(template)) || ((modes.length) && (!modes.includes("placeholder")))) + test.skip(name, () => null) + else + test(name, async () => expect(await placeholder.run({ template, base: 0, ...query, ...input })).toBe(true), timeout) + } + }))