OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / floor-view.test.js

101 lines · javascript ref:
import { test } from 'brittle'
import { readFileSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import { parse as acornParse } from '../vendor/acorn/dist/acorn.mjs'

const HERE = dirname(fileURLToPath(import.meta.url))
const LIB = resolve(HERE, '../lib')

// The floor view is served as real files, so nothing lints or type-checks it and
// a stray brace ships a white page. Same gate as render.test.js, same reason.

test('floor view: level.js parses as a module', (t) => {
  const src = readFileSync(resolve(LIB, 'floor/level.js'), 'utf-8')
  try {
    acornParse(src, { ecmaVersion: 'latest', sourceType: 'module' })
    t.pass('parses')
  } catch (err) {
    t.fail('level.js is not valid JS: ' + err.message)
  }
})

test('floor view: no duplicate top-level function declarations', (t) => {
  const src = readFileSync(resolve(LIB, 'floor/level.js'), 'utf-8')
  const ast = acornParse(src, { ecmaVersion: 'latest', sourceType: 'module' })
  const seen = new Set()
  const dupes = []
  for (const node of ast.body) {
    if (node.type !== 'FunctionDeclaration' || !node.id) continue
    if (seen.has(node.id.name)) dupes.push(node.id.name)
    seen.add(node.id.name)
  }
  // A second declaration silently replaces the first for the whole module —
  // exactly the escapeHtml bug that shipped in render.js.
  t.alike(dupes, [], 'no name declared twice: ' + dupes.join(', '))
})

test('floor view: every function it calls is declared or imported', (t) => {
  // A parse gate cannot catch a MISSING function — the file still parses fine.
  // This test exists because a scripted extraction removed `load` and
  // `currentSymbol` along with the functions it was told to cut, and the page
  // would have shipped broken with every other gate green.
  const src = readFileSync(resolve(LIB, 'floor/level.js'), 'utf-8')
  const ast = acornParse(src, { ecmaVersion: 'latest', sourceType: 'module' })

  const declared = new Set()
  for (const n of ast.body) {
    if (n.type === 'FunctionDeclaration') declared.add(n.id.name)
    if (n.type === 'VariableDeclaration') {
      for (const d of n.declarations) if (d.id.type === 'Identifier') declared.add(d.id.name)
    }
    if (n.type === 'ImportDeclaration') {
      for (const sp of n.specifiers) declared.add(sp.local.name)
    }
  }

  const called = new Set()
  const walk = (node) => {
    if (!node || typeof node.type !== 'string') return
    if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
      called.add(node.callee.name)
    }
    for (const k of Object.keys(node)) {
      const v = node[k]
      if (Array.isArray(v)) v.forEach(walk)
      else if (v && typeof v === 'object' && typeof v.type === 'string') walk(v)
    }
  }
  walk(ast)

  const builtins = new Set(['fetch', 'parseInt', 'parseFloat', 'String', 'Number',
    'Set', 'Map', 'Array', 'Object', 'encodeURIComponent', 'decodeURIComponent', 'isNaN'])
  const missing = [...called].filter(n => !declared.has(n) && !builtins.has(n))
  t.alike(missing, [], 'nothing calls a function that does not exist: ' + missing.join(', '))
})

test('floor view: the card builders live in a module node can import', (t) => {
  const src = readFileSync(resolve(LIB, 'floor/level.js'), 'utf-8')
  t.ok(src.includes("from '/floor/cards.js'"), 'the view imports them')
  t.absent(/function buildSymbolLevel/.test(src), 'and does not define its own copy')

  const server = readFileSync(resolve(LIB, 'server.js'), 'utf-8')
  t.ok(server.includes("'/floor/cards.js'"), 'the server routes that path')
})

test('floor view: it imports the real layout module, not a copy', (t) => {
  const src = readFileSync(resolve(LIB, 'floor/level.js'), 'utf-8')
  t.ok(src.includes("from '/floor/layout.js'"), 'imports the served layout')

  const server = readFileSync(resolve(LIB, 'server.js'), 'utf-8')
  t.ok(server.includes("'/floor/layout.js'"), 'and the server routes that path')
  t.ok(server.includes("sendFile(req, res, 'layout.js'"), 'to lib/layout.js itself')
})

test('floor view: the page loads the script it is served with', (t) => {
  const html = readFileSync(resolve(LIB, 'floor/level.html'), 'utf-8')
  t.ok(html.includes('src="/floor/level.js"'), 'script tag points at the served path')
  t.ok(html.includes('type="module"'), 'as a module, since it imports layout')
  t.ok(html.includes('id="world"'), 'and the SVG group the view draws into exists')
})