Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 143 additions & 1 deletion frontend/src/components/data-table/__tests__/columns.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* Copyright 2024 Marimo. All rights reserved. */

import { render } from "@testing-library/react";
import { I18nProvider } from "react-aria";
import { describe, expect, it, test } from "vitest";
import { TooltipProvider } from "@/components/ui/tooltip";
import { generateColumns, inferFieldTypes } from "../columns";
import { generateColumns, inferFieldTypes, LocaleNumber } from "../columns";
import { getMimeValues, isMimeValue, MimeCell } from "../mime-cell";
import type { FieldTypesWithExternalType } from "../types";
import { uniformSample } from "../uniformSample";
Expand Down Expand Up @@ -377,3 +378,144 @@ describe("getMimeValues", () => {
expect(getMimeValues(values)).toBeUndefined();
});
});

describe("LocaleNumber", () => {
it("should format numbers correctly for en-US locale", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={1_234_567.89} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"1,234,567.89"`);
});

it("should format numbers correctly for de-DE locale", () => {
const { container } = render(
<I18nProvider locale="de-DE">
<LocaleNumber value={1_234_567.89} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"1.234.567,89"`);
});

it("should format integers correctly", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={1000} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"1,000"`);
});

it("should format zero correctly", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={0} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"0"`);
});

it("should format negative numbers correctly", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={-1234.56} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"-1,234.56"`);
});

it("should format small decimal numbers correctly", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={0.123_456_789} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"0.123456789"`);
});

it("should format large numbers correctly", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={999_999_999.99} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"999,999,999.99"`);
});

it("should format numbers correctly for fr-FR locale", () => {
const { container } = render(
<I18nProvider locale="fr-FR">
<LocaleNumber value={1_234_567.89} />
</I18nProvider>,
);
// eslint-disable-next-line no-irregular-whitespace
expect(container.textContent).toMatchInlineSnapshot(`"1 234 567,89"`);
});

it("should format numbers correctly for ja-JP locale", () => {
const { container } = render(
<I18nProvider locale="ja-JP">
<LocaleNumber value={1_234_567.89} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"1,234,567.89"`);
});

it("should respect maximumFractionDigits based on locale", () => {
// Test with a number that has many decimal places
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={1.123_456_789_012_345_7} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"1.1234567890123457"`);
});

it("should handle very large numbers", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={123_456_789_012_345.67} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(
`"123,456,789,012,345.67"`,
);
});

it("should handle Infinity", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={Infinity} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"∞"`);
});

it("should handle negative Infinity", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={-Infinity} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"-∞"`);
});

it("should handle NaN", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={Number.NaN} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"NaN"`);
});

it("should handle numbers with scientific notation", () => {
const { container } = render(
<I18nProvider locale="en-US">
<LocaleNumber value={1e10} />
</I18nProvider>,
);
expect(container.textContent).toMatchInlineSnapshot(`"10,000,000,000"`);
});
});
10 changes: 7 additions & 3 deletions frontend/src/components/data-table/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import { PopoverClose } from "@radix-ui/react-popover";
import type { Column, ColumnDef } from "@tanstack/react-table";
import { formatDate } from "date-fns";
import { useNumberFormatter } from "react-aria";
import { useLocale, useNumberFormatter } from "react-aria";
import { WithLocale } from "@/core/i18n/with-locale";
import type { DataType } from "@/core/kernel/messages";
import type { CalculateTopKRows } from "@/plugins/impl/DataTablePlugin";
import { cn } from "@/utils/cn";
import { type DateFormat, exactDateTime, getDateFormat } from "@/utils/dates";
import { Logger } from "@/utils/Logger";
import { Maps } from "@/utils/maps";
import { maxFractionalDigits } from "@/utils/numbers";
import { Objects } from "@/utils/objects";
import { EmotionCacheProvider } from "../editor/output/EmotionCacheProvider";
import { JsonOutput } from "../editor/output/JsonOutput";
Expand Down Expand Up @@ -594,7 +595,10 @@ export function renderCellValue<TData, TValue>({
);
}

const LocaleNumber = ({ value }: { value: number }) => {
const format = useNumberFormatter();
export const LocaleNumber = ({ value }: { value: number }) => {
const { locale } = useLocale();
const format = useNumberFormatter({
maximumFractionDigits: maxFractionalDigits(locale),
});
return format.format(value);
};
36 changes: 18 additions & 18 deletions frontend/src/core/cells/__tests__/logs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/plain",
channel: "stdout",
data: "Hello, World!",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -37,7 +37,7 @@ describe("getCellLogsForMessage", () => {

expect(logs).toHaveLength(1);
expect(logs[0]).toEqual({
timestamp: 1234567890,
timestamp: 1_234_567_890,
level: "stdout",
message: "Hello, World!",
cellId: "cell-1",
Expand All @@ -52,7 +52,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/plain",
channel: "stderr",
data: "Error occurred",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -65,7 +65,7 @@ describe("getCellLogsForMessage", () => {

expect(logs).toHaveLength(1);
expect(logs[0]).toEqual({
timestamp: 1234567890,
timestamp: 1_234_567_890,
level: "stderr",
message: "Error occurred",
cellId: "cell-2",
Expand All @@ -80,7 +80,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/html",
channel: "stdout",
data: '<span style="color: red;">Error: Something went wrong</span>',
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -93,7 +93,7 @@ describe("getCellLogsForMessage", () => {

expect(logs).toHaveLength(1);
expect(logs[0]).toEqual({
timestamp: 1234567890,
timestamp: 1_234_567_890,
level: "stdout",
message: "Error: Something went wrong",
cellId: "cell-3",
Expand All @@ -108,7 +108,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/html",
channel: "stderr",
data: "<div><strong>Critical Error:</strong> System failure</div>",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -121,7 +121,7 @@ describe("getCellLogsForMessage", () => {

expect(logs).toHaveLength(1);
expect(logs[0]).toEqual({
timestamp: 1234567890,
timestamp: 1_234_567_890,
level: "stderr",
message: "Critical Error: System failure",
cellId: "cell-4",
Expand All @@ -136,7 +136,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "application/vnd.marimo+traceback",
channel: "marimo-error",
data: '<div class="traceback"><span style="color: red;">Traceback (most recent call last):</span><pre> File "test.py", line 1</pre></div>',
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -162,19 +162,19 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/plain",
channel: "stdout",
data: "Plain text output",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
{
mimetype: "text/html",
channel: "stdout",
data: "<span>HTML output</span>",
timestamp: 1234567891,
timestamp: 1_234_567_891,
},
{
mimetype: "application/vnd.marimo+traceback",
channel: "stderr",
data: "<div>Traceback error</div>",
timestamp: 1234567892,
timestamp: 1_234_567_892,
},
],
output: null,
Expand Down Expand Up @@ -225,7 +225,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "application/json",
channel: "stdout",
data: '{"key": "value"}',
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -247,7 +247,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/plain",
channel: "pdb" as unknown as "stdout", // Non-logging channel
data: "Should be ignored",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand Down Expand Up @@ -284,7 +284,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/html",
channel: "stdout",
data: "<div><span>Nested</span> <strong>HTML</strong> <em>content</em></div>",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -307,7 +307,7 @@ describe("getCellLogsForMessage", () => {
mimetype: "text/plain",
channel: "marimo-error",
data: "Internal error",
timestamp: 1234567890,
timestamp: 1_234_567_890,
},
],
output: null,
Expand All @@ -326,7 +326,7 @@ describe("getCellLogsForMessage", () => {
describe("formatLogTimestamp", () => {
test("formats unix timestamp correctly", () => {
// January 1, 2024, 12:00:00 PM UTC
const timestamp = 1704110400;
const timestamp = 1_704_110_400;
const result = formatLogTimestamp(timestamp);

// The result depends on the timezone, so we just check it's not empty
Expand All @@ -336,7 +336,7 @@ describe("formatLogTimestamp", () => {
});

test("formats timestamp with AM/PM notation", () => {
const timestamp = 1704110400; // Noon
const timestamp = 1_704_110_400; // Noon
const result = formatLogTimestamp(timestamp);

// Should contain AM or PM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { expect, test } from "vitest";
import { prettyTime } from "../ProgressPlugin";

const Cases: Array<[number, string]> = [
const Cases: [number, string][] = [
// exact values
[0, "0s"],
[1, "1s"],
Expand Down
Loading