Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
161 lines
6.3 KiB
JavaScript
161 lines
6.3 KiB
JavaScript
const rgb2hex = require('../index')
|
|
const typeofErrorMessage = 'color has to be type of `string`'
|
|
const invalidErrorMessage = (input) => 'given color (' + input + ') isn\'t a valid rgb or rgba color'
|
|
|
|
describe('rgb2hex should', () => {
|
|
describe('throw an error if input is not typeof string', () => {
|
|
it('[Object] {color: \'something\'}', () => {
|
|
const input = {color: 'something'}
|
|
expect(() => rgb2hex(input)).toThrow(typeofErrorMessage)
|
|
})
|
|
|
|
it('[Function] function(){}', () => {
|
|
const input = function(){}
|
|
expect(() => rgb2hex(input)).toThrow(typeofErrorMessage)
|
|
})
|
|
|
|
it('[Number] 231', () => {
|
|
const input = 231
|
|
expect(() => rgb2hex(input)).toThrow(typeofErrorMessage)
|
|
})
|
|
})
|
|
|
|
describe('throw an error if input is invalid', () => {
|
|
it('notacolor', () => {
|
|
const input = 'notacolor'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
})
|
|
|
|
it('rgba(100, 100)', () => {
|
|
const input = 'rgb(100, 100)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
})
|
|
|
|
it('rgba(100, 10a0, 200, 300)', () => {
|
|
const input = 'rgba(100, 10a0, 200, 300)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
})
|
|
|
|
it('rgba(23, 54, 4, -.33)', () => {
|
|
const input = 'rgba(23, 54, 4, -.33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
})
|
|
})
|
|
|
|
it('return input if it is already a hex color', () => {
|
|
const input = '#ffffff'
|
|
const parsedValue = rgb2hex(input)
|
|
|
|
expect(parsedValue).toHaveProperty('hex')
|
|
expect(parsedValue).toHaveProperty('alpha')
|
|
expect(typeof parsedValue.hex).toEqual('string')
|
|
expect(parsedValue.hex).toEqual('#ffffff')
|
|
expect(typeof parsedValue.alpha).toEqual('number')
|
|
expect(parsedValue.alpha).toEqual(1)
|
|
})
|
|
|
|
describe('parse input properly', () => {
|
|
it('converting rgb(210,43,255)', () => {
|
|
const input = 'rgb(210,43,255)'
|
|
const parsedValue = rgb2hex(input)
|
|
|
|
expect(parsedValue).toHaveProperty('hex')
|
|
expect(parsedValue).toHaveProperty('alpha')
|
|
expect(typeof parsedValue.hex).toEqual('string')
|
|
expect(parsedValue.hex).toEqual('#d22bff')
|
|
expect(typeof parsedValue.alpha).toEqual('number')
|
|
expect(parsedValue.alpha).toEqual(1)
|
|
})
|
|
|
|
it('converting rgba(12,173,22,.67313)', () => {
|
|
const input = 'rgba(12,173,22,.67313)'
|
|
const parsedValue = rgb2hex(input)
|
|
|
|
expect(parsedValue).toHaveProperty('hex')
|
|
expect(parsedValue).toHaveProperty('alpha')
|
|
expect(typeof parsedValue.hex).toEqual('string')
|
|
expect(parsedValue.hex).toEqual('#0cad16')
|
|
expect(typeof parsedValue.alpha).toEqual('number')
|
|
expect(parsedValue.alpha).toEqual(0.67)
|
|
})
|
|
|
|
it('by limiting alpha value to 1', () => {
|
|
const input = 'rgba(236,68,44,1)'
|
|
expect(rgb2hex(input).alpha).not.toBeGreaterThan(1)
|
|
})
|
|
|
|
it('by not accepting to big values', () => {
|
|
let input = 'rgba(1123, 54, 4, 0.33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
input = 'rgba(113, 1154, 4, 0.33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
input = 'rgba(113, 154, 1114, 0.33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
input = 'rgba(113, 54, 4, 2.33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
input = 'rgbaaaaaa(113, 54, 4, .33)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
input = 'rgba(12,173,22,1.67)'
|
|
expect(() => rgb2hex(input)).toThrow(invalidErrorMessage(input))
|
|
})
|
|
|
|
it('transparent color', () => {
|
|
const input = 'rgba(0, 0, 0, 0)'
|
|
expect(rgb2hex(input).alpha).toBe(0)
|
|
expect(rgb2hex(input).hex).toBe('#000000')
|
|
})
|
|
|
|
it('double digit alpha values', () => {
|
|
const input = 'rgba(0, 0, 0, 1.00)'
|
|
expect(rgb2hex(input).alpha).toBe(1)
|
|
expect(rgb2hex(input).hex).toBe('#000000')
|
|
})
|
|
})
|
|
|
|
describe('not care about', () => {
|
|
it('rgb or rgba prefix', () => {
|
|
const rgb = 'rgb(0, 0, 0)'
|
|
const rgba = 'rgba(0, 0, 0)'
|
|
|
|
expect(rgb2hex(rgb).hex).toEqual(rgb2hex(rgba).hex)
|
|
})
|
|
|
|
it('spaces between color numbers', () => {
|
|
const rgbWithSpaces = 'rgb(0, 0, 0)'
|
|
const rgbaWithoutSpaces = 'rgba(0,0,0)'
|
|
|
|
expect(rgb2hex(rgbWithSpaces).hex).toEqual(rgb2hex(rgbaWithoutSpaces).hex)
|
|
})
|
|
|
|
it('if alpha value starts with `.` or with `0`', () => {
|
|
const rgbaAlphaStartsWithDot = 'rgba(213,12,4,.45)'
|
|
const rgbaAlphaStartsWithZero = 'rgba(213,12,4,0.45)'
|
|
|
|
expect(rgb2hex(rgbaAlphaStartsWithDot).alpha).toEqual(rgb2hex(rgbaAlphaStartsWithZero).alpha)
|
|
})
|
|
|
|
it('optional terminating semicolon', () => {
|
|
const rgbWithTerminatingSemicolon = 'rgb(0,0,0);'
|
|
const rgbWithoutTerminatingSemicolon = 'rgb(0,0,0)'
|
|
|
|
expect(rgb2hex(rgbWithTerminatingSemicolon).hex).toEqual(rgb2hex(rgbWithoutTerminatingSemicolon).hex)
|
|
})
|
|
|
|
it('stuff that is appended', () => {
|
|
expect(rgb2hex('rgb(0,0,0)0px0px8px').hex).toEqual(rgb2hex('rgb(0,0,0)').hex)
|
|
expect(rgb2hex('rgb(0,0,0)solid2px').hex).toEqual(rgb2hex('rgb(0,0,0)').hex)
|
|
})
|
|
|
|
it('stuff that is prepended', () => {
|
|
expect(rgb2hex('0px0px8pxrgb(0,0,0)').hex).toEqual(rgb2hex('rgb(0,0,0)').hex)
|
|
expect(rgb2hex('solid2pxrgb(0,0,0)').hex).toEqual(rgb2hex('rgb(0,0,0)').hex)
|
|
})
|
|
|
|
it('stuff that is prepended and appended', () => {
|
|
const url = 'https://foo.bar.com/123.abc.456'
|
|
const values = `url("${url}")no-repeatscroll0%0%/100%padding-boxborder-box`
|
|
expect(rgb2hex(`${values}rgb(226,230,233)${values}`).hex)
|
|
expect(rgb2hex(`${values}rgba(226,230,233,0.4)${values}`).hex)
|
|
})
|
|
})
|
|
}) |