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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ target/
node_modules
.df
test_status.json
/apps/landing/public/test-status/
.venv
__pycache__
.pytest_cache
Expand Down
17 changes: 4 additions & 13 deletions apps/landing/src/app/test-case/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
TEST_CASE_FILTERS,
TEST_CASE_FILTERS_MAP,
} from '@/constants'
import type { TestStatusMap, TestStatusPageManifest } from '@/types'
import type { TestStatusMap } from '@/types'

export const metadata: Metadata = {
title: '테스트 케이스 - 한국·영어 점자 표준 검증',
Expand Down Expand Up @@ -83,16 +83,13 @@ export const metadata: Metadata = {
}

export default async function TestCasePage() {
const [testStatus, ruleMap, testStatusPageManifest] = await Promise.all([
const [testStatus, ruleMap] = await Promise.all([
readFile('../../test_status.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<TestStatusMap>,
readFile('../../rule_map.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<Record<string, { title: string; description: string }>>,
readFile('public/test-status/manifest.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<TestStatusPageManifest>,
])

// Dynamically create filter map based on rule_map keys
Expand Down Expand Up @@ -175,10 +172,8 @@ export default async function TestCasePage() {
</Text>
</VStack>
<TestCaseResults
pageInfo={testStatusPageManifest[key]}
pageSize={category === 'corpus' ? 250 : undefined}
results={testStatus[key][6]}
statusKey={key}
total={testStatus[key][0]}
/>
</TestCaseRuleContainer>
{currentClause !== nextClause && (
Expand All @@ -190,11 +185,7 @@ export default async function TestCasePage() {
})

return (
<TestCaseProvider
filterMap={filterMap}
filterTotalMap={filterTotalMap}
testStatusMap={testStatus}
>
<TestCaseProvider filterMap={filterMap} filterTotalMap={filterTotalMap}>
<SideBarProvider>
<Box maxW="1520px" mx="auto" pb="40px" w="100%">
<VStack
Expand Down
6 changes: 0 additions & 6 deletions apps/landing/src/components/test-case/TestCaseProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import { createContext, useContext, useState } from 'react'

import type { TestStatusMap } from '@/types'

export type TestCaseFilter =
| 'korean'
| 'math'
Expand Down Expand Up @@ -32,7 +30,6 @@ export type FilterTotalMap = Record<
>

const TestCaseContext = createContext<{
testStatusMap: TestStatusMap
filterMap: FilterMap
filterTotalMap: FilterTotalMap
options: TestCaseOptions
Expand All @@ -48,12 +45,10 @@ export function useTestCase() {
}

export function TestCaseProvider({
testStatusMap,
filterMap,
filterTotalMap,
children,
}: {
testStatusMap: TestStatusMap
filterMap: FilterMap
filterTotalMap: FilterTotalMap
children: React.ReactNode
Expand All @@ -74,7 +69,6 @@ export function TestCaseProvider({
filterTotalMap,
onChangeOptions: handleChangeOptions,
options,
testStatusMap,
}}
>
{children}
Expand Down
99 changes: 21 additions & 78 deletions apps/landing/src/components/test-case/TestCaseResults.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,31 @@
'use client'

import { Button, Flex, Text, VStack } from '@devup-ui/react'
import { useEffect, useState } from 'react'
import { useState } from 'react'

import type { TestStatus, TestStatusPageInfo } from '@/types'
import type { TestStatus } from '@/types'

import { TestCaseList } from './list/TestCaseList'
import { TestCaseTable } from './table/TestCaseTable'
import { useTestCase } from './TestCaseProvider'

interface TestCaseResultsProps {
pageInfo?: TestStatusPageInfo
pageSize?: number
results: TestStatus[6]
statusKey: string
total: number
}

/**
* Displays inline test results or lazily loads every page of a large result set.
* Prerenders test results from the build-generated status data and paginates
* large result sets locally without additional network requests.
*/
export function TestCaseResults({
pageInfo,
results,
statusKey,
total,
}: TestCaseResultsProps) {
export function TestCaseResults({ pageSize, results }: TestCaseResultsProps) {
const { options } = useTestCase()
const [page, setPage] = useState(1)
const [pagedResults, setPagedResults] = useState<TestStatus[6]>([])
const [isLoading, setIsLoading] = useState(Boolean(pageInfo))
const [error, setError] = useState('')

useEffect(() => {
if (!pageInfo) return

const abortController = new AbortController()
const encodedStatusKey = statusKey
.split('/')
.map((segment) => encodeURIComponent(segment))
.join('/')

setIsLoading(true)
setError('')
fetch(`/test-status/${encodedStatusKey}/page-${page}.json`, {
signal: abortController.signal,
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json() as Promise<TestStatus[6]>
})
.then((nextResults) => {
setPagedResults(nextResults)
setIsLoading(false)
})
.catch((fetchError: unknown) => {
if (
fetchError instanceof DOMException &&
fetchError.name === 'AbortError'
) {
return
}
setError('테스트 케이스를 불러오지 못했습니다.')
setIsLoading(false)
})

return () => abortController.abort()
}, [page, pageInfo, statusKey])
const pageCount = pageSize ? Math.ceil(results.length / pageSize) : 1
const startIndex = pageSize ? (page - 1) * pageSize : 0
const visibleResults = pageSize
? results.slice(startIndex, startIndex + pageSize)
: results

function handleFirstPage() {
setPage(1)
Expand All @@ -78,30 +36,25 @@ export function TestCaseResults({
}

function handleNextPage() {
if (!pageInfo) return
setPage((currentPage) => Math.min(pageInfo.pageCount, currentPage + 1))
setPage((currentPage) => Math.min(pageCount, currentPage + 1))
}

function handleLastPage() {
if (!pageInfo) return
setPage(pageInfo.pageCount)
setPage(pageCount)
}

const visibleResults = pageInfo ? pagedResults : results
const startIndex = pageInfo ? (page - 1) * pageInfo.pageSize : 0

return (
<VStack gap="20px">
{pageInfo ? (
{pageSize ? (
<Flex
alignItems="center"
flexWrap="wrap"
gap="8px"
justifyContent="space-between"
>
<Text color="$caption" typography="body">
{startIndex + 1}–{Math.min(startIndex + pageInfo.pageSize, total)} /{' '}
{total.toLocaleString()}건
{startIndex + 1}–{Math.min(startIndex + pageSize, results.length)} /{' '}
{results.length.toLocaleString()}건
</Text>
<Flex alignItems="center" gap="8px">
<Button
Expand Down Expand Up @@ -131,15 +84,15 @@ export function TestCaseResults({
이전
</Button>
<Text color="$text" typography="body">
{page.toLocaleString()} / {pageInfo.pageCount.toLocaleString()}
{page.toLocaleString()} / {pageCount.toLocaleString()}
</Text>
<Button
_disabled={{ cursor: 'not-allowed', opacity: 0.4 }}
border="solid 1px $primary"
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === pageInfo.pageCount}
disabled={page === pageCount}
onClick={handleNextPage}
px="12px"
py="6px"
Expand All @@ -152,7 +105,7 @@ export function TestCaseResults({
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === pageInfo.pageCount}
disabled={page === pageCount}
onClick={handleLastPage}
px="12px"
py="6px"
Expand All @@ -162,20 +115,10 @@ export function TestCaseResults({
</Flex>
</Flex>
) : null}
{isLoading ? (
<Text color="$caption" typography="body">
테스트 케이스를 불러오는 중입니다.
</Text>
) : null}
{error ? (
<Text color="$error" typography="body">
{error}
</Text>
) : null}
{!isLoading && !error && options.type === 'table' ? (
{options.type === 'table' ? (
<TestCaseTable results={visibleResults} startIndex={startIndex} />
) : null}
{!isLoading && !error && options.type === 'list' ? (
{options.type === 'list' ? (
<TestCaseList results={visibleResults} />
) : null}
</VStack>
Expand Down
7 changes: 0 additions & 7 deletions apps/landing/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,3 @@ export type TestStatus = [
]

export type TestStatusMap = Record<string, TestStatus>

export interface TestStatusPageInfo {
pageSize: number
pageCount: number
}

export type TestStatusPageManifest = Record<string, TestStatusPageInfo>
55 changes: 0 additions & 55 deletions libs/braillify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,15 +1306,6 @@ mod test {
bool,
);

#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct TestStatusPageInfo {
page_size: usize,
page_count: usize,
}

const TEST_STATUS_PAGE_SIZE: usize = 250;

#[derive(Default)]
struct NiklFailureStats {
encoding_errors: usize,
Expand Down Expand Up @@ -1727,52 +1718,6 @@ mod test {
println!("총 Skip: {}건", skipped_cases.len());
}

// Large, sharded fixture groups are emitted as browser-loadable pages.
// Their aggregate counts stay in `test_status.json`, while the row list
// is loaded only after the matching landing-page tab is opened. This
// keeps every row available without embedding the entire corpus in the
// statically rendered page payload.
let paged_status_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../apps/landing/public/test-status");
let mut paged_status_manifest = std::collections::BTreeMap::new();
for (key, config) in &rule_map {
if !config.shards {
continue;
}

let stats = file_stats
.get_mut(key)
.unwrap_or_else(|| panic!("missing test status for sharded group {key}"));
let rows = std::mem::take(&mut stats.6);
let page_count = rows.len().div_ceil(TEST_STATUS_PAGE_SIZE);
let output_dir = paged_status_root.join(key);
std::fs::create_dir_all(&output_dir).unwrap_or_else(|error| {
panic!(
"failed to create paged test-status directory {}: {error}",
output_dir.display()
)
});

for (page_index, page) in rows.chunks(TEST_STATUS_PAGE_SIZE).enumerate() {
let page_path = output_dir.join(format!("page-{}.json", page_index + 1));
serde_json::to_writer(File::create(&page_path).unwrap(), page).unwrap();
}

paged_status_manifest.insert(
key.clone(),
TestStatusPageInfo {
page_size: TEST_STATUS_PAGE_SIZE,
page_count,
},
);
}
std::fs::create_dir_all(&paged_status_root).unwrap();
serde_json::to_writer_pretty(
File::create(paged_status_root.join("manifest.json")).unwrap(),
&paged_status_manifest,
)
.unwrap();

// Write per-file stats to the workspace-root status file.
let status_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../test_status.json");
serde_json::to_writer_pretty(File::create(status_path).unwrap(), &file_stats).unwrap();
Expand Down
Loading