From 4675b461d8889ffafc5743476420dc253cdcddb6 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 3 Nov 2026 14:38:27 +0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(table):=20=D0=B7=D0=B0=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D1=87=D0=BD=D1=8F=20AURORA-1892=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=81=D0=B2=D0=B5=D1=80=D0=BA=D0=B5=2002.11.202?= =?UTF-8?q?6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validation.js: добавлены колонки, пропущенные в первой реализации (разделы 5/6.1 сверки) — approved.*, collegial.approved, ckk.*, contract_detail.* (включая amount_foreign), corrected_plan, pay_ho/pay_rf, booking, rem_actual/ residual_after_actual, qN.total, new_plan, data.qN.economy. Отдельно добавлен transfer_econ формы 2 — явное исключение из общего префикса transfer_*, так как перечень Салмина прямо требует контроль минуса для этих колонок (раздел 6.3, противоречие с Word). Тест validationUtils.test.js обновлён под это исключение и дополнен проверкой полноты перечня и отсутствия дублей с remaining-booking. FORM_1/OPER.js, AHR.js, CAP.js: перестановка колонок Q4 — «Комментарий» теперь перед «Скорректированный план 4 кв.» во всех пяти таблицах (раздел 8 сверки). Не включено (требует отдельного решения заказчика, раздел 12.2 сверки): контроль месячного факта/СПОД, контроль факта расшифровочных листов сверх плановых колонок, область действия правила для формы 4. Co-Authored-By: Claude Sonnet 5 --- .../RealtimeTable/constants/FORM_1/AHR.js | 24 ++--- .../RealtimeTable/constants/FORM_1/CAP.js | 16 ++-- .../RealtimeTable/constants/FORM_1/OPER.js | 16 ++-- .../RealtimeTable/constants/validation.js | 94 ++++++++++++++++++- .../utils/validationUtils.test.js | 54 ++++++++++- 5 files changed, 174 insertions(+), 30 deletions(-) diff --git a/web/src/components/RealtimeTable/constants/FORM_1/AHR.js b/web/src/components/RealtimeTable/constants/FORM_1/AHR.js index 37aeb0f..f5ab8eb 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/AHR.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/AHR.js @@ -3568,14 +3568,14 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2026_skorr_plan_4_kv_26', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { - header: 'Сумма, тыс. руб. (без НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', - size: 250, + header: 'Сумма, тыс. руб. (с учётом НДС)', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', + size: 300, filterFn: 'contains', }, ], @@ -3587,14 +3587,14 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2026_skorr_plan_4_kv_26', columns: [ { - header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', - size: 300, + header: 'Сумма, тыс. руб. (без НДС)', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', + size: 250, filterFn: 'contains', }, ], diff --git a/web/src/components/RealtimeTable/constants/FORM_1/CAP.js b/web/src/components/RealtimeTable/constants/FORM_1/CAP.js index e8bce1b..6731fa5 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/CAP.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/CAP.js @@ -3582,13 +3582,13 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', size: 300, filterFn: 'contains', }, @@ -3601,13 +3601,13 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', size: 300, filterFn: 'contains', }, diff --git a/web/src/components/RealtimeTable/constants/FORM_1/OPER.js b/web/src/components/RealtimeTable/constants/FORM_1/OPER.js index 2d1378e..4e5f839 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/OPER.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/OPER.js @@ -3566,13 +3566,13 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', size: 300, filterFn: 'contains', }, @@ -3585,13 +3585,13 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', size: 300, filterFn: 'contains', }, diff --git a/web/src/components/RealtimeTable/constants/validation.js b/web/src/components/RealtimeTable/constants/validation.js index 197f6f9..1e62c14 100644 --- a/web/src/components/RealtimeTable/constants/validation.js +++ b/web/src/components/RealtimeTable/constants/validation.js @@ -88,7 +88,10 @@ const bookingLessBalanceRuleScope = { // Перечень — из вложения Салмина М.П. к AURORA-1892 (AURORA-1892_Перечень_колонок.md), // уточнённого кодовыми именами колонок (см. рулинги в PR). Корректировки и переносы // (adj_*, base_correction, revision_*, seq_*, target_change, transfer_*) сюда не входят — -// там минус — нормальная операция. +// там минус — нормальная операция. Исключение — transfer_econ (перенос в фонд экономии, +// формы 2): перечень Салмина прямо требует контроль для этих колонок (см. сверку +// AURORA-1892 от 02.11.2026, раздел 6.3); общий префикс transfer_* на этот ключ не +// распространяется. export const nonNegativeColumnKeys = [ // Основные листы форм 1, 2, 4 (Операц/АХР/КВ): план по кварталам и за год. "data.plan.q1", @@ -165,6 +168,95 @@ export const nonNegativeColumnKeys = [ "data.amount_q4", "data.limit_with_vat", "data.limit_without_vat", + + // Доработка по сверке AURORA-1892 от 02.11.2026 (разделы 5, 6.1): колонки из + // исходного перечня Салмина, пропущенные при первой реализации. + // Платежи следующих лет и сумма других ССП. + "data.contract_summary.future_y1", + "data.contract_summary.future_y2", + "data.contract_summary.other_ssp", + + // Утверждённый план по кварталам и за год. + "data.approved.q1", + "data.approved.q2", + "data.approved.q3", + "data.approved.q4", + "data.approved.year", + + // Сумма коллегиального органа. + "data.collegial.approved", + + // Расходы ЦКК по кварталам и по сметам РФ. + "data.ckk.q1", + "data.ckk.q2", + "data.ckk.q3", + "data.ckk.q4", + "data.ckk.rf_schedule", + + // Расходы договора по кварталам, по сметам РФ и сумма в валюте. + "data.contract_detail.q1", + "data.contract_detail.q2", + "data.contract_detail.q3", + "data.contract_detail.q4", + "data.contract_detail.rf_schedule", + "data.contract_detail.amount_foreign", + + // Скорректированный план по кварталам. + "data.q1.corrected_plan", + "data.q2.corrected_plan", + "data.q3.corrected_plan", + "data.q4.corrected_plan", + + // Части платежа ГО и РФ. + "data.q1.pay_ho", + "data.q1.pay_rf", + "data.q2.pay_ho", + "data.q2.pay_rf", + "data.q3.pay_ho", + "data.q3.pay_rf", + "data.q4.pay_ho", + "data.q4.pay_rf", + + // Бронь квартала (отдельно от ввода брони на 2026/2027 годы выше). + "data.q1.booking", + "data.q2.booking", + "data.q3.booking", + "data.q4.booking", + + // Остаток после факта (формы 1 и 2 используют разные кодовые имена). + "data.q1.rem_actual", + "data.q2.rem_actual", + "data.q3.rem_actual", + "data.q4.rem_actual", + "data.q1.residual_after_actual", + "data.q2.residual_after_actual", + "data.q3.residual_after_actual", + "data.q4.residual_after_actual", + + // Перенос в фонд экономии формы 1 (data.economy — другой ключ, чем transfer_econ + // формы 2 ниже, и не входит в исключённый префикс transfer_*). + "data.q1.economy", + "data.q2.economy", + "data.q3.economy", + "data.q4.economy", + + // Перенос в фонд экономии формы 2 — исключение из transfer_*, см. комментарий + // к nonNegativeColumnKeys выше. + "data.q1.transfer_econ", + "data.q2.transfer_econ", + "data.q3.transfer_econ", + "data.q4.transfer_econ", + + // Всего при закрытии квартала. + "data.q1.total", + "data.q2.total", + "data.q3.total", + "data.q4.total", + + // Новый план на подпись (кварталов II–IV; для I квартала поле отсутствует). + "data.q2.new_plan", + "data.q3.new_plan", + "data.q4.new_plan", ]; const isNegativeNumber = (value) => diff --git a/web/src/components/RealtimeTable/utils/validationUtils.test.js b/web/src/components/RealtimeTable/utils/validationUtils.test.js index 6989933..0d9782c 100644 --- a/web/src/components/RealtimeTable/utils/validationUtils.test.js +++ b/web/src/components/RealtimeTable/utils/validationUtils.test.js @@ -51,11 +51,15 @@ test('a scoped rule is disabled outside its form/sheet scope', () => { assert.equal(isValidationRuleEnabled(rule, { formType: 'FORM_2', sheetName: 'AHR' }), false); }); -test('the shared nonNegativeColumnKeys list excludes correction and transfer columns', () => { +test('the shared nonNegativeColumnKeys list excludes correction and transfer columns, except the explicit transfer_econ carve-out', () => { const excludedPrefixes = ['adj_', 'base_correction', 'revision_', 'seq_', 'target_change', 'transfer_']; + // AURORA-1892, сверка 02.11.2026 (раздел 6.3): перечень Салмина прямо требует контроль + // минуса для transfer_econ, в отличие от остальных transfer_* — это намеренное исключение. + const allowedException = 'transfer_econ'; for (const key of nonNegativeColumnKeys) { const segment = key.split('.').pop(); + if (segment === allowedException) continue; for (const prefix of excludedPrefixes) { assert.equal( segment.startsWith(prefix), @@ -65,3 +69,51 @@ test('the shared nonNegativeColumnKeys list excludes correction and transfer col } } }); + +test('nonNegativeColumnKeys has no duplicate entries', () => { + assert.equal(nonNegativeColumnKeys.length, new Set(nonNegativeColumnKeys).size); +}); + +test('nonNegativeColumnKeys covers the gaps confirmed by the AURORA-1892 audit (02.11.2026)', () => { + const mustInclude = [ + // Разделы 5/6.1 сверки: обязательные показатели, пропущенные в первой реализации. + 'data.contract_summary.future_y1', + 'data.contract_summary.other_ssp', + 'data.approved.q1', + 'data.approved.year', + 'data.collegial.approved', + 'data.ckk.q1', + 'data.ckk.rf_schedule', + 'data.contract_detail.q1', + 'data.contract_detail.amount_foreign', + 'data.q4.corrected_plan', + 'data.q1.pay_ho', + 'data.q1.pay_rf', + 'data.q1.booking', + 'data.q1.rem_actual', + 'data.q1.residual_after_actual', + 'data.q4.total', + 'data.q2.new_plan', + // Раздел 6.3: исключение из transfer_* по прямому требованию перечня Салмина. + 'data.q1.transfer_econ', + 'data.q4.transfer_econ', + // Раздел 5: экономия формы 1 — другой ключ, чем transfer_econ, не затронут исключением. + 'data.q1.economy', + ]; + + for (const key of mustInclude) { + assert.ok(nonNegativeColumnKeys.includes(key), `${key} must be in nonNegativeColumnKeys`); + } + + // Д-статус из раздела 5/6.1: эти остатки уже покрыты правилом remaining-booking + // и не должны дублироваться здесь (иначе пользователь увидит две причины ошибки). + const mustExclude = [ + 'data.q1.rem_booking', + 'data.q4.rem_booking', + 'data.q1.residual_after_booking', + 'data.q4.residual_after_booking', + ]; + for (const key of mustExclude) { + assert.ok(!nonNegativeColumnKeys.includes(key), `${key} is already covered by remaining-booking and must not duplicate here`); + } +}); -- 2.47.2 From 20f1bed33655e5f9e2a87e2a2b6ae542aaad21bf Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 8 Nov 2026 17:09:29 +0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(table):=20=D0=BC=D0=B8=D0=BD=D1=83?= =?UTF-8?q?=D1=81=20=D0=BA=D1=80=D0=B0=D1=81=D0=BD=D1=8B=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=BD=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D1=83=D0=B5=D0=BC=D1=8B=D1=85=20=D1=8F=D1=87=D0=B5=D0=B9?= =?UTF-8?q?=D0=BA=D0=B0=D1=85=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B2=D0=B2=D0=BE=D0=B4=D0=B0=20(AURORA-1892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Возврат тестирования 06.11, критерий «любое отрицательное число выделено цветом»: NOT_EDITABLE_INPUT_STYLES (AURORA-1890) разворачивался после цвета текста и перекрашивал минус в серый #5F6B7A — в расчётных колонках вне перечня отрицательные значения оставались серыми. Цвет текста теперь решает resolveCellTextColor (utils/cellTextColor.js, с тестами): минус — красный всегда, серый — только для неотрицательных значений. Co-Authored-By: Claude Opus 5.5 --- .../RealtimeTable/Cell/Cell/Cell.jsx | 39 ++++-------------- .../RealtimeTable/utils/cellTextColor.js | 40 +++++++++++++++++++ .../RealtimeTable/utils/cellTextColor.test.js | 29 ++++++++++++++ 3 files changed, 77 insertions(+), 31 deletions(-) create mode 100644 web/src/components/RealtimeTable/utils/cellTextColor.js create mode 100644 web/src/components/RealtimeTable/utils/cellTextColor.test.js diff --git a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx index e97cd3c..39f37f7 100644 --- a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx +++ b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx @@ -1,6 +1,7 @@ import { Tooltip } from '@mui/material'; import React, { useMemo, useCallback, useState } from 'react'; import { useCellLock } from '../../contexts/RealtimeContext'; +import { resolveCellTextColor } from '../../utils/cellTextColor'; import { formatNumber } from '../../utils/numberFormat'; import { TEXT_COLUMN_IDS } from '../../utils/moneyUtils'; @@ -33,11 +34,6 @@ const INVALID_STYLES = { color: '#C60C0C', }; -// AURORA-1892: отрицательные суммы должны быть явно отличимы от нуля и обычных чисел, -// в том числе на тёмных фонах палитры (например ROOT красной палитры #933634). -const NEGATIVE_COLOR_ON_LIGHT = '#C62828'; -const NEGATIVE_COLOR_ON_DARK = '#FFCDD2'; - // AURORA-1890: редактируемая ячейка строки INPUT — белый фон и заметная // синяя рамка (вместо прежней почти незаметной полупрозрачной 2px #d8e4ea94). // Толщина рамки растёт при наведении (1px → 2px, см. isHovered в CellComponent), курсор — text. @@ -50,10 +46,11 @@ const EDITABLE_BORDER_COLOR = '#7FA7C9'; // AURORA-1890: нередактируемая ячейка строки INPUT (вычисляемая или вне // текущего этапа) — приглушённый фон и текст, чтобы визуально отличаться от // редактируемой белой ячейки без отдельной палитры цветов. +// Цвет текста (в т.ч. приглушённый серый) задаёт resolveCellTextColor: здесь его нет, +// чтобы стиль не перекрывал красный цвет отрицательных значений (AURORA-1892). const NOT_EDITABLE_INPUT_STYLES = { cursor: 'default', backgroundColor: '#F4F5F7', - color: '#5F6B7A', }; // Строки ITEM/GROUP/ROOT: вне скоупа AURORA-1890 — сохраняем прежнее поведение. @@ -104,26 +101,6 @@ const OVER_LIMIT_CHIP_STYLES = { const NAME_COLUMN_IDS = new Set(['data.header.name', 'data.name']); -const getColorBrightness = (hexColor) => { - if (!hexColor) return 255; - const color = hexColor.replace('#', ''); - let r; - let g; - let b; - if (color.length === 3) { - r = Number.parseInt(color[0] + color[0], 16); - g = Number.parseInt(color[1] + color[1], 16); - b = Number.parseInt(color[2] + color[2], 16); - } else if (color.length === 6) { - r = Number.parseInt(color.substring(0, 2), 16); - g = Number.parseInt(color.substring(2, 4), 16); - b = Number.parseInt(color.substring(4, 6), 16); - } else { - return 255; - } - return 0.299 * r + 0.587 * g + 0.114 * b; -}; - // Оптимизированная функция подсветки const createHighlightedContent = (originalValue, displayValue, searchQueries) => { const cleanQueries = searchQueries.filter(Boolean); @@ -213,11 +190,11 @@ const CellComponent = ({ // AURORA-1890: занятая коллегой ячейка больше не красится в серый — // обычный brightness-based цвет текста, отличие только в рамке/бейдже. - const textColor = useMemo(() => { - const brightness = getColorBrightness(backgroundColor); - if (isNegative) return brightness < 128 ? NEGATIVE_COLOR_ON_DARK : NEGATIVE_COLOR_ON_LIGHT; - return brightness < 128 ? '#ffffff' : '#000000'; - }, [backgroundColor, isNegative]); + const isMutedInput = isInputRow && !isEditable; + const textColor = useMemo( + () => resolveCellTextColor({ backgroundColor, isNegative, isMutedInput }), + [backgroundColor, isNegative, isMutedInput], + ); // AURORA-1890: нередактируемый стиль зависит от типа строки (INPUT получает // приглушённую заливку, ITEM/GROUP/ROOT — прежнее поведение без изменений). diff --git a/web/src/components/RealtimeTable/utils/cellTextColor.js b/web/src/components/RealtimeTable/utils/cellTextColor.js new file mode 100644 index 0000000..583ce33 --- /dev/null +++ b/web/src/components/RealtimeTable/utils/cellTextColor.js @@ -0,0 +1,40 @@ +// Цвет текста ячейки таблицы (вынесено из Cell.jsx, чтобы покрыть тестами без DOM). + +// AURORA-1892: отрицательные суммы должны быть явно отличимы от нуля и обычных чисел, +// в том числе на тёмных фонах палитры (например ROOT красной палитры #933634). +export const NEGATIVE_COLOR_ON_LIGHT = '#C62828'; +export const NEGATIVE_COLOR_ON_DARK = '#FFCDD2'; + +// AURORA-1890: приглушённый текст нередактируемой ячейки строки ввода (фон #F4F5F7). +export const MUTED_INPUT_TEXT_COLOR = '#5F6B7A'; + +export const getColorBrightness = (hexColor) => { + if (!hexColor) return 255; + const color = hexColor.replace('#', ''); + let r; + let g; + let b; + if (color.length === 3) { + r = Number.parseInt(color[0] + color[0], 16); + g = Number.parseInt(color[1] + color[1], 16); + b = Number.parseInt(color[2] + color[2], 16); + } else if (color.length === 6) { + r = Number.parseInt(color.substring(0, 2), 16); + g = Number.parseInt(color.substring(2, 4), 16); + b = Number.parseInt(color.substring(4, 6), 16); + } else { + return 255; + } + return 0.299 * r + 0.587 * g + 0.114 * b; +}; + +// AURORA-1892 (возврат 06.11): минус красный в любой ячейке. Раньше серый цвет +// нередактируемой ячейки строки ввода (AURORA-1890) перекрывал красный, и в расчётных +// колонках отрицательные значения оставались серыми. Фон такой ячейки всегда светлый +// (#F4F5F7), поэтому для неё — красный для светлого фона. +export const resolveCellTextColor = ({ backgroundColor, isNegative, isMutedInput }) => { + if (isMutedInput) return isNegative ? NEGATIVE_COLOR_ON_LIGHT : MUTED_INPUT_TEXT_COLOR; + const isDark = getColorBrightness(backgroundColor) < 128; + if (isNegative) return isDark ? NEGATIVE_COLOR_ON_DARK : NEGATIVE_COLOR_ON_LIGHT; + return isDark ? '#ffffff' : '#000000'; +}; diff --git a/web/src/components/RealtimeTable/utils/cellTextColor.test.js b/web/src/components/RealtimeTable/utils/cellTextColor.test.js new file mode 100644 index 0000000..7dea8eb --- /dev/null +++ b/web/src/components/RealtimeTable/utils/cellTextColor.test.js @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + MUTED_INPUT_TEXT_COLOR, + NEGATIVE_COLOR_ON_DARK, + NEGATIVE_COLOR_ON_LIGHT, + resolveCellTextColor, +} from './cellTextColor.js'; + +// AURORA-1892 (возврат 06.11): в расчётных (нередактируемых) ячейках строки ввода +// минус оставался серым — серый цвет AURORA-1890 перекрывал красный. +test('a negative value in a non-editable input cell is red, not muted grey', () => { + assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: true, isMutedInput: true }), NEGATIVE_COLOR_ON_LIGHT); +}); + +test('a non-negative value in a non-editable input cell keeps the muted grey', () => { + assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: false, isMutedInput: true }), MUTED_INPUT_TEXT_COLOR); +}); + +test('negative values follow background brightness outside muted input cells', () => { + assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: true, isMutedInput: false }), NEGATIVE_COLOR_ON_LIGHT); + assert.equal(resolveCellTextColor({ backgroundColor: '#933634', isNegative: true, isMutedInput: false }), NEGATIVE_COLOR_ON_DARK); +}); + +test('regular values are black on light and white on dark backgrounds', () => { + assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: false, isMutedInput: false }), '#000000'); + assert.equal(resolveCellTextColor({ backgroundColor: '#933634', isNegative: false, isMutedInput: false }), '#ffffff'); + assert.equal(resolveCellTextColor({ backgroundColor: undefined, isNegative: false, isMutedInput: false }), '#000000'); +}); -- 2.47.2