solverforge-ui 0.6.1

Frontend component library for SolverForge constraint-optimization applications
Documentation
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');

const { createDom } = require('./support/fake-dom');

const ROOT = path.resolve(__dirname, '..');

function loadSf(files, overrides = {}) {
  const { document, window, Node } = createDom();
  const context = vm.createContext({
    console,
    document,
    window,
    Node,
    navigator: {
      clipboard: {
        writeText() {
          return Promise.resolve();
        },
      },
    },
    setTimeout,
    clearTimeout,
    ...overrides,
  });

  files.forEach((file) => {
    const source = fs.readFileSync(path.join(ROOT, file), 'utf8');
    vm.runInContext(source, context, { filename: file });
  });

  return { SF: context.window.SF, document };
}

test('status bar constraint dots keep stable ids for solver analysis coloring', () => {
  const { SF } = loadSf(['js-src/00-core.js', 'js-src/01-score.js', 'js-src/05-statusbar.js']);

  const statusBar = SF.createStatusBar({
    constraints: [
      { name: 'Hard A', type: 'hard' },
      { name: 'Soft B', type: 'soft' },
    ],
  });

  const dots = statusBar.el.querySelectorAll('.sf-constraint-dot');
  assert.equal(dots[0].id, 'sf-cdot-0');
  assert.equal(dots[1].id, 'sf-cdot-1');
});

test('modal, toast, and api guide copy controls expose aria-label attributes', () => {
  const { SF } = loadSf([
    'js-src/00-core.js',
    'js-src/06-modal.js',
    'js-src/09-toast.js',
    'js-src/12-api-guide.js',
  ]);

  const modal = SF.createModal({ title: 'Example', body: 'Body' });
  const modalClose = modal.el.querySelector('.sf-modal-close');
  assert.equal(modalClose.attributes['aria-label'], 'Close modal');

  SF.showToast({ message: 'Saved' });
  const toastBtn = modal.el.ownerDocument.body.querySelector('.sf-toast-close');
  assert.equal(toastBtn.attributes['aria-label'], 'Dismiss toast');

  const guide = SF.createApiGuide({
    endpoints: [{ path: '/x', curl: 'curl /x' }],
  });
  const copyBtn = guide.querySelector('.sf-copy-btn');
  assert.equal(copyBtn.attributes['aria-label'], 'Copy command');
});

test('reduced-motion CSS only targets solverforge scoped classes', () => {
  const css = fs.readFileSync(path.join(ROOT, 'css-src/14-animations.css'), 'utf8');

  assert.match(css, /\[class\^="sf-"\]/);
  assert.doesNotMatch(css, /@media \(prefers-reduced-motion: reduce\)\s*\{\s*\*,/);
});