printhive/test/filaments.test.js
dlawler489 8f3e63585c Let a slot ask for any colour
Some prints don't care about the shade, and some change with the order. Each
slot in the editor now has an "any colour" box: ticking it greys out the swatch
and saves the slot without a colour, which the matcher fills from any spool of
that material. A slot added by hand starts this way, since adding one usually
means you care about the material rather than the shade.

An unparseable colour already became "any colour" rather than being refused;
that stays, and is now covered by tests along with the shapes the checkbox
actually sends — null, empty string, and no colour key at all.

Worth knowing when reading the printable count: ticking "any colour" makes a
model easier to satisfy, so the count now partly reflects your decisions rather
than only what the project files ask for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:19:47 +10:00

132 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const test = require('node:test');
const assert = require('node:assert/strict');
const filaments = require('../lib/filaments');
const spool = (material, hex, name) => ({ id: hex, material, colour_hex: hex, colour: name || hex, remaining_g: 500 });
// The model that started this: four PLA slots, one spool of Jade White.
const pooh = [
{ type: 'PLA', colour: '#000000' },
{ type: 'PLA', colour: '#586579' },
{ type: 'PLA', colour: '#8E9089' },
{ type: 'PLA', colour: '#FFFFFF' },
];
test('a four-colour print needs those colours, not four spools of anything', () => {
const shelf = [spool('PLA', '#ffffff', 'Jade White')];
const slots = filaments.assignSpools(pooh, shelf);
assert.deepEqual(slots.map((s) => s.filled), [false, false, false, true]);
assert.equal(slots[3].by, 'Jade White');
assert.equal(filaments.isPrintable(pooh, shelf), false);
assert.equal(filaments.shortfall(pooh, shelf).length, 3);
// Four white spools still can't print it: the other three colours are missing.
const fourWhite = ['a', 'b', 'c', 'd'].map((k) => ({ ...spool('PLA', '#ffffff', 'Jade White'), id: k }));
assert.equal(filaments.isPrintable(pooh, fourWhite), false);
});
test('with every colour on the shelf it prints', () => {
const shelf = [spool('PLA', '#000000', 'Black'), spool('PLA', '#586579', 'Blue Grey'),
spool('PLA', '#8E9089', 'Grey'), spool('PLA', '#ffffff', 'Jade White')];
assert.equal(filaments.isPrintable(pooh, shelf), true);
assert.deepEqual(filaments.assignSpools(pooh, shelf).map((s) => s.by),
['Black', 'Blue Grey', 'Grey', 'Jade White']);
});
test('one spool cannot fill two slots of the same colour', () => {
const twoBlack = [{ type: 'PLA', colour: '#000000' }, { type: 'PLA', colour: '#000000' }];
assert.equal(filaments.isPrintable(twoBlack, [spool('PLA', '#000000', 'Black')]), false);
assert.equal(filaments.isPrintable(twoBlack,
[spool('PLA', '#000000', 'Black'), { ...spool('PLA', '#000000', 'Black 2'), id: 'b2' }]), true);
});
test('8-digit hex with alpha parses as its colour', () => {
assert.deepEqual(filaments.rgb('#FFFFFFFF'), { r: 255, g: 255, b: 255 });
assert.deepEqual(filaments.rgb('#586579'), { r: 88, g: 101, b: 121 });
assert.equal(filaments.rgb('not a colour'), null);
// A slot written with alpha still matches a plain spool of that colour.
assert.equal(filaments.isPrintable([{ type: 'PLA', colour: '#FFFFFFFF' }],
[spool('PLA', '#ffffff', 'Jade White')]), true);
});
test('near shades count, but near-black is not black', () => {
const nearWhite = [{ type: 'PLA', colour: '#FDFDFD' }];
assert.equal(filaments.isPrintable(nearWhite, [spool('PLA', '#ffffff', 'Jade White')]), true);
// #161616 sits about 66 away from black, beyond the tolerance of 50.
assert.ok(filaments.colourDistance('#000000', '#161616') > filaments.COLOUR_TOLERANCE);
assert.equal(filaments.isPrintable([{ type: 'PLA', colour: '#161616' }],
[spool('PLA', '#000000', 'Black')]), false);
});
test('the nearest spool is named when a slot can’t be filled', () => {
const [slot] = filaments.assignSpools([{ type: 'PLA', colour: '#000000' }],
[spool('PLA', '#ffffff', 'Jade White')]);
assert.equal(slot.filled, false);
assert.equal(slot.nearest.name, 'Jade White');
assert.ok(slot.nearest.distance > 0);
});
test('material still matters, and a slot with no colour takes any spool of it', () => {
assert.equal(filaments.isPrintable([{ type: 'PETG', colour: '#ffffff' }],
[spool('PLA', '#ffffff', 'Jade White')]), false);
assert.equal(filaments.isPrintable([{ type: 'PLA', colour: null }],
[spool('PLA', '#123456', 'Anything')]), true);
});
test('an empty spool doesn’t count, but an unweighed one does', () => {
const one = [{ type: 'PLA', colour: '#ffffff' }];
assert.equal(filaments.isPrintable(one, [{ ...spool('PLA', '#ffffff', 'Jade White'), remaining_g: 0 }]), false);
assert.equal(filaments.isPrintable(one, [{ ...spool('PLA', '#ffffff', 'Jade White'), remaining_g: null }]), true);
});
test('a model we know nothing about is not claimed to be printable', () => {
assert.equal(filaments.isPrintable(null, [spool('PLA', '#ffffff')]), false);
assert.equal(filaments.isPrintable([], [spool('PLA', '#ffffff')]), false);
assert.deepEqual(filaments.assignSpools(null, []), []);
});
test('slots sent from the page are cleaned before they reach the database', () => {
const cleaned = filaments.cleanSlots([
{ type: ' pla ', colour: '#ffffff' }, // trimmed, hex upper-cased
{ type: 'PETG', colour: 'not a colour' }, // bad colour becomes "any colour"
{ type: '', colour: '#000000' }, // no material: dropped entirely
{ type: 'PLA', colour: '#8e9089' },
]);
assert.deepEqual(cleaned, [
{ type: 'pla', colour: '#FFFFFF' },
{ type: 'PETG', colour: null },
{ type: 'PLA', colour: '#8E9089' },
]);
});
test('anything that isn’t a list of slots is refused outright', () => {
assert.equal(filaments.cleanSlots(null), null);
assert.equal(filaments.cleanSlots('PLA'), null);
assert.equal(filaments.cleanSlots({ type: 'PLA' }), null);
assert.deepEqual(filaments.cleanSlots([]), []);
});
test('"any colour" survives the trip from the page to the database', () => {
// What the editor sends when the box is ticked, plus the two shapes that can
// arrive from older clients or a hand-made request.
assert.deepEqual(filaments.cleanSlots([
{ type: 'PLA', colour: null },
{ type: 'PETG', colour: '' },
{ type: 'ABS' },
]), [
{ type: 'PLA', colour: null },
{ type: 'PETG', colour: null },
{ type: 'ABS', colour: null },
]);
});
test('a slot with no colour is filled by any spool of that material', () => {
const anyWhite = [{ type: 'PLA', colour: null }];
assert.equal(filaments.isPrintable(anyWhite,
[{ id: 1, material: 'PLA', colour_hex: '#123456', colour: 'Anything', remaining_g: 200 }]), true);
// …but still not by the wrong material, and still one spool per slot.
assert.equal(filaments.isPrintable(anyWhite,
[{ id: 1, material: 'PETG', colour_hex: '#123456', colour: 'Anything', remaining_g: 200 }]), false);
assert.equal(filaments.isPrintable([{ type: 'PLA', colour: null }, { type: 'PLA', colour: null }],
[{ id: 1, material: 'PLA', colour_hex: '#123456', colour: 'Anything', remaining_g: 200 }]), false);
});