diff --git a/packages/components/demo/App.tsx b/packages/components/demo/App.tsx index 4105088b..4e446963 100644 --- a/packages/components/demo/App.tsx +++ b/packages/components/demo/App.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Page } from '../src/index.js' -import { Source, createHttpFileSystem, getSource } from '../src/index.ts' +import { getHttpSource } from '../src/index.ts' export interface Navigation { col?: number row?: number @@ -15,10 +15,6 @@ function getNumberParam(search: URLSearchParams, key: string): number | undefine return number } -const fileSystems = [ - createHttpFileSystem(), -] - export default function App() { const search = new URLSearchParams(location.search) const url = search.get('url') @@ -31,20 +27,13 @@ export default function App() { const row = getNumberParam(search, 'row') const col = getNumberParam(search, 'col') - let source: Source | undefined = undefined - for (const fileSystem of fileSystems) { - const fsSource = getSource(url, fileSystem) - if (fsSource){ - source = fsSource - break - } - } + const source = getHttpSource(url) if (!source) { return
Could not load a data source. You have to pass a valid source in the url, eg: {defaultUrl}.
} return `/?url=${sourceId}`, getCellRouteUrl: ({ sourceId, col, row }) => `/?url=${sourceId}&col=${col}&row=${row}`, diff --git a/packages/components/src/components/Breadcrumb.tsx b/packages/components/src/components/Breadcrumb.tsx index ee39ec13..748f1235 100644 --- a/packages/components/src/components/Breadcrumb.tsx +++ b/packages/components/src/components/Breadcrumb.tsx @@ -1,5 +1,5 @@ import { RoutesConfig } from '../lib/routes.js' -import { Source } from '../lib/source.js' +import { Source } from '../lib/sources/types.js' export type BreadcrumbConfig = RoutesConfig interface BreadcrumbProps { diff --git a/packages/components/src/components/Cell.tsx b/packages/components/src/components/Cell.tsx index 3d15a1ef..f7aa573b 100644 --- a/packages/components/src/components/Cell.tsx +++ b/packages/components/src/components/Cell.tsx @@ -1,7 +1,7 @@ import { asyncRows } from 'hightable' import { asyncBufferFromUrl, parquetMetadataAsync } from 'hyparquet' import { useEffect, useState } from 'react' -import { FileSource } from '../lib/source.js' +import { FileSource } from '../lib/sources/types.js' import { parquetDataFrame } from '../lib/tableProvider.js' import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js' import Layout from './Layout.js' diff --git a/packages/components/src/components/File.tsx b/packages/components/src/components/File.tsx index 24248fd6..743b5cc6 100644 --- a/packages/components/src/components/File.tsx +++ b/packages/components/src/components/File.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { FileSource } from '../lib/source.js' +import { FileSource } from '../lib/sources/types.js' import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js' import Layout from './Layout.js' import Viewer, { ViewerConfig } from './viewers/Viewer.js' diff --git a/packages/components/src/components/Folder.tsx b/packages/components/src/components/Folder.tsx index b05b7218..bec5f9b1 100644 --- a/packages/components/src/components/Folder.tsx +++ b/packages/components/src/components/Folder.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import type { DirSource, FileMetadata } from '../lib/source.js' +import type { DirSource, FileMetadata } from '../lib/sources/types.js' import { cn, formatFileSize, getFileDate, getFileDateShort } from '../lib/utils.js' import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js' import Layout, { Spinner } from './Layout.js' diff --git a/packages/components/src/components/Page.tsx b/packages/components/src/components/Page.tsx index 3c80de83..37d76a1a 100644 --- a/packages/components/src/components/Page.tsx +++ b/packages/components/src/components/Page.tsx @@ -1,4 +1,4 @@ -import { Source } from '../lib/source.js' +import { Source } from '../lib/sources/types.js' import { BreadcrumbConfig } from './Breadcrumb.js' import Cell from './Cell.js' import File, { FileConfig } from './File.js' diff --git a/packages/components/src/components/index.ts b/packages/components/src/components/index.ts index b3464170..567ef046 100644 --- a/packages/components/src/components/index.ts +++ b/packages/components/src/components/index.ts @@ -2,9 +2,9 @@ import Breadcrumb, { BreadcrumbConfig } from './Breadcrumb.js' import Cell, { CellConfig } from './Cell.js' import File, { FileConfig } from './File.js' import Folder, { FolderConfig } from './Folder.js' -import Layout from './Layout.js' +import Layout, { Spinner } from './Layout.js' import Markdown from './Markdown.js' import Page, { PageConfig } from './Page.js' export * from './viewers/index.js' -export { Breadcrumb, Cell, File, Folder, Layout, Markdown, Page } +export { Breadcrumb, Cell, File, Folder, Layout, Markdown, Page, Spinner } export type { BreadcrumbConfig, CellConfig, FileConfig, FolderConfig, PageConfig } diff --git a/packages/components/src/components/viewers/ImageView.tsx b/packages/components/src/components/viewers/ImageView.tsx index 3cb3a583..20063d04 100644 --- a/packages/components/src/components/viewers/ImageView.tsx +++ b/packages/components/src/components/viewers/ImageView.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { FileSource } from '../../lib/source.js' +import { FileSource } from '../../lib/sources/types.js' import { contentTypes, parseFileSize } from '../../lib/utils.js' import { Spinner } from '../Layout.js' import ContentHeader from './ContentHeader.js' diff --git a/packages/components/src/components/viewers/MarkdownView.tsx b/packages/components/src/components/viewers/MarkdownView.tsx index 17c1acd1..ac98e0fe 100644 --- a/packages/components/src/components/viewers/MarkdownView.tsx +++ b/packages/components/src/components/viewers/MarkdownView.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { FileSource } from '../../lib/source.js' +import { FileSource } from '../../lib/sources/types.js' import { parseFileSize } from '../../lib/utils.js' import { Spinner } from '../Layout.js' import Markdown from '../Markdown.js' diff --git a/packages/components/src/components/viewers/ParquetView.tsx b/packages/components/src/components/viewers/ParquetView.tsx index d8906fb6..b37b43e2 100644 --- a/packages/components/src/components/viewers/ParquetView.tsx +++ b/packages/components/src/components/viewers/ParquetView.tsx @@ -2,7 +2,7 @@ import HighTable, { DataFrame, rowCache } from 'hightable' import { asyncBufferFromUrl, parquetMetadataAsync } from 'hyparquet' import React, { useCallback, useEffect, useState } from 'react' import { RoutesConfig, appendSearchParams } from '../../lib/routes.js' -import { FileSource } from '../../lib/source.js' +import { FileSource } from '../../lib/sources/types.js' import { parquetDataFrame } from '../../lib/tableProvider.js' import { Spinner } from '../Layout.js' import CellPanel from './CellPanel.js' diff --git a/packages/components/src/components/viewers/TextView.tsx b/packages/components/src/components/viewers/TextView.tsx index 066486de..d6805073 100644 --- a/packages/components/src/components/viewers/TextView.tsx +++ b/packages/components/src/components/viewers/TextView.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { FileSource } from '../../lib/source.js' +import { FileSource } from '../../lib/sources/types.js' import { parseFileSize } from '../../lib/utils.js' import { Spinner } from '../Layout.js' import ContentHeader, { TextContent } from './ContentHeader.js' diff --git a/packages/components/src/components/viewers/Viewer.tsx b/packages/components/src/components/viewers/Viewer.tsx index 9bf34abf..82020680 100644 --- a/packages/components/src/components/viewers/Viewer.tsx +++ b/packages/components/src/components/viewers/Viewer.tsx @@ -1,4 +1,4 @@ -import { FileSource } from '../../lib/source.js' +import { FileSource } from '../../lib/sources/types.js' import { imageTypes } from '../../lib/utils.js' import ImageView from './ImageView.js' import MarkdownView from './MarkdownView.js' diff --git a/packages/components/src/lib/filesystem.ts b/packages/components/src/lib/filesystem.ts deleted file mode 100644 index f16feae9..00000000 --- a/packages/components/src/lib/filesystem.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { DirSource, FileKind, FileMetadata, FileSource, SourcePart } from './source.js' -import { getFileName } from './utils.js' - -export interface FileSystem { - fsId: string - canParse: (sourceId: string) => boolean - getKind: (sourceId: string) => FileKind - getFileName: (sourceId: string) => string - getPrefix: (sourceId: string) => string - getResolveUrl: (sourceId: string) => string - getSourceParts: (sourceId: string) => SourcePart[] - listFiles: (prefix: string, options?: {requestInit?: RequestInit}) => Promise - getSource?: (sourceId: string, options?: {requestInit?: RequestInit}) => FileSource | DirSource -} - -export function getSource(sourceId: string, fs: FileSystem, options?: {requestInit?: RequestInit}): FileSource | DirSource | undefined { - if (fs.getSource) { - /// if the file system provides an optimized getSource method, use it - return fs.getSource(sourceId, options) - } - if (!fs.canParse(sourceId)) { - return undefined - } - const sourceParts = fs.getSourceParts(sourceId) - if (fs.getKind(sourceId) === 'file') { - return { - kind: 'file', - sourceId, - sourceParts, - fileName: fs.getFileName(sourceId), - resolveUrl: fs.getResolveUrl(sourceId), - requestInit: options?.requestInit, - } - } else { - const prefix = fs.getPrefix(sourceId) - return { - kind: 'directory', - sourceId, - sourceParts, - prefix, - listFiles: () => fs.listFiles(prefix, { requestInit: options?.requestInit }), - } - } -} - -function notImplemented(): never { - throw new Error('Not implemented') -} - -// Built-in implementations -export function createHttpFileSystem(): FileSystem { - return { - fsId: 'http' as const, - canParse: sourceId => URL.canParse(sourceId), - getKind: () => 'file', /// all the URLs are considered files - getFileName, - getPrefix: notImplemented, - getResolveUrl: sourceId => sourceId, - getSourceParts: sourceId => [{ text: sourceId, sourceId }], - listFiles: notImplemented, - } -} - -export interface HyperparamFileMetadata { - key: string - eTag?: string - fileSize?: number - lastModified: string -} -async function fetchHyperparamFilesList(prefix: string, endpoint: string, options?: {requestInit?: RequestInit}): Promise { - const url = new URL('/api/store/list', endpoint) - url.searchParams.append('prefix', prefix) - const res = await fetch(url, options?.requestInit) - if (res.ok) { - return await res.json() as HyperparamFileMetadata[] - } else { - throw new Error(await res.text()) - } -} -export function createHyperparamFileSystem({ endpoint }: {endpoint: string}): FileSystem { - if (!URL.canParse(endpoint)) { - throw new Error('Invalid endpoint') - } - function getKind(sourceId: string): FileKind { - return sourceId === '' || sourceId.endsWith('/') ? 'directory' : 'file' - } - return { - fsId: 'hyperparam' as const, - canParse: (sourceId: string): boolean => { - /// we expect relative paths, such as path/to/file or path/to/dir/ - /// let's just check that it is empty or starts with a "word" character - return sourceId === '' || /^[\w]/.test(sourceId) - }, - getKind, - getFileName, - getPrefix: sourceId => sourceId.replace(/\/$/, ''), - getResolveUrl: (sourceId: string): string => { - const url = new URL('/api/store/get', endpoint) - url.searchParams.append('key', sourceId) - return url.toString() - }, - getSourceParts: (sourceId: string): SourcePart[] => { - const parts = sourceId.split('/') - return [ - { 'text': '/', 'sourceId': '' }, - ...parts.map((part, depth) => { - const slashSuffix = depth === parts.length - 1 ? '' : '/' - return { - text: part + slashSuffix, - sourceId: parts.slice(0, depth + 1).join('/') + slashSuffix, - } - }), - ] - }, - listFiles: async (prefix, options): Promise => { - const files = await fetchHyperparamFilesList(prefix, endpoint, options) - return files.map(file => ({ - name: file.key, - eTag: file.eTag, - size: file.fileSize, - lastModified: file.lastModified, - sourceId: (prefix === '' ? '' : prefix + '/') + file.key, - kind: getKind(file.key), - })) - }, - } -} diff --git a/packages/components/src/lib/index.ts b/packages/components/src/lib/index.ts index ca373038..13bfa866 100644 --- a/packages/components/src/lib/index.ts +++ b/packages/components/src/lib/index.ts @@ -1,9 +1,7 @@ -export { createHttpFileSystem, createHyperparamFileSystem, getSource } from './filesystem.js' -export type { FileSystem } from './filesystem.js' export { appendSearchParams, replaceSearchParams } from './routes.js' export type { RoutesConfig } from './routes.js' -export type { DirSource, FileKind, FileMetadata, FileSource, Source, SourcePart } from './source.js' +export * from './sources/index.js' export { parquetDataFrame } from './tableProvider.js' -export { asyncBufferFrom, cn, contentTypes, formatFileSize, getFileDate, getFileDateShort, getFileName, imageTypes, parseFileSize } from './utils.js' +export { asyncBufferFrom, cn, contentTypes, formatFileSize, getFileDate, getFileDateShort, imageTypes, parseFileSize } from './utils.js' export { parquetQueryWorker } from './workers/parquetWorkerClient.js' export type { AsyncBufferFrom, Row } from './workers/types.js' diff --git a/packages/components/src/lib/sources/httpSource.ts b/packages/components/src/lib/sources/httpSource.ts new file mode 100644 index 00000000..a80d9bb3 --- /dev/null +++ b/packages/components/src/lib/sources/httpSource.ts @@ -0,0 +1,16 @@ +import { FileSource } from './types.js' +import { getFileName } from './utils.js' + +export function getHttpSource(sourceId: string, options?: {requestInit?: RequestInit}): FileSource | undefined { + if (!URL.canParse(sourceId)) { + return undefined + } + return { + kind: 'file', + sourceId, + sourceParts: [{ text: sourceId, sourceId }], + fileName: getFileName(sourceId), + resolveUrl: sourceId, + requestInit: options?.requestInit, + } +} diff --git a/packages/components/src/lib/sources/hyperparamSource.ts b/packages/components/src/lib/sources/hyperparamSource.ts new file mode 100644 index 00000000..eb833fab --- /dev/null +++ b/packages/components/src/lib/sources/hyperparamSource.ts @@ -0,0 +1,93 @@ +import type { DirSource, FileKind, FileMetadata, FileSource, SourcePart } from './types.js' +import { getFileName } from './utils.js' + +export interface HyperparamFileMetadata { + key: string + eTag?: string + fileSize?: number + lastModified: string +} + +function canParse(sourceId: string): boolean { + /// we expect relative paths, such as path/to/file or path/to/dir/ + /// let's just check that it is empty or starts with a "word" character + return sourceId === '' || /^[\w]/.test(sourceId) +} + +function getSourceParts(sourceId: string): SourcePart[] { + const parts = sourceId.split('/') + return [ + { 'text': '/', 'sourceId': '' }, + ...parts.map((part, depth) => { + const slashSuffix = depth === parts.length - 1 ? '' : '/' + return { + text: part + slashSuffix, + sourceId: parts.slice(0, depth + 1).join('/') + slashSuffix, + } + }), + ] +} + +function getKind(sourceId: string): FileKind { + return sourceId === '' || sourceId.endsWith('/') ? 'directory' : 'file' +} + +function getResolveUrl(sourceId: string, { endpoint }: {endpoint: string}): string { + const url = new URL('/api/store/get', endpoint) + url.searchParams.append('key', sourceId) + return url.toString() +} + +function getPrefix(sourceId: string): string { + return sourceId.replace(/\/$/, '') +} + +async function listFiles(prefix: string, { endpoint, requestInit }: {endpoint: string, requestInit?: RequestInit}): Promise { + const url = new URL('/api/store/list', endpoint) + url.searchParams.append('prefix', prefix) + const res = await fetch(url, requestInit) + if (res.ok) { + const files = await res.json() as HyperparamFileMetadata[] + return files.map(file => ({ + name: file.key, + eTag: file.eTag, + size: file.fileSize, + lastModified: file.lastModified, + sourceId: (prefix === '' ? '' : prefix + '/') + file.key, + kind: getKind(file.key), + })) + } else { + throw new Error(await res.text()) + } +} + +export function getHyperparamSource(sourceId: string, { endpoint, requestInit }: {endpoint: string, requestInit?: RequestInit}): FileSource | DirSource | undefined { + if (!URL.canParse(endpoint)) { + throw new Error('Invalid endpoint') + } + if (!canParse(sourceId)) { + return undefined + } + const sourceParts = getSourceParts(sourceId) + if (getKind(sourceId) === 'file') { + return { + kind: 'file', + sourceId, + sourceParts, + fileName: getFileName(sourceId), + resolveUrl: getResolveUrl(sourceId, { endpoint }), + requestInit, + } + } else { + const prefix = getPrefix(sourceId) + return { + kind: 'directory', + sourceId, + sourceParts, + prefix, + listFiles: () => listFiles(prefix, { endpoint, requestInit }), + } + } +} + + diff --git a/packages/components/src/lib/sources/index.ts b/packages/components/src/lib/sources/index.ts new file mode 100644 index 00000000..52437851 --- /dev/null +++ b/packages/components/src/lib/sources/index.ts @@ -0,0 +1,5 @@ +export { getHttpSource } from './httpSource.js' +export { getHyperparamSource } from './hyperparamSource.js' +export type { HyperparamFileMetadata } from './hyperparamSource.js' +export type { DirSource, FileKind, FileMetadata, FileSource, Source, SourcePart } from './types.js' +export { getFileName } from './utils.js' diff --git a/packages/components/src/lib/source.ts b/packages/components/src/lib/sources/types.ts similarity index 99% rename from packages/components/src/lib/source.ts rename to packages/components/src/lib/sources/types.ts index 0a2e54f0..c3ccafec 100644 --- a/packages/components/src/lib/source.ts +++ b/packages/components/src/lib/sources/types.ts @@ -33,4 +33,3 @@ export interface DirSource extends BaseSource { } export type Source = FileSource | DirSource - diff --git a/packages/components/src/lib/sources/utils.ts b/packages/components/src/lib/sources/utils.ts new file mode 100644 index 00000000..4733e93d --- /dev/null +++ b/packages/components/src/lib/sources/utils.ts @@ -0,0 +1,8 @@ +export function getFileName(source: string): string { + const fileName = source + .replace(/\?.*$/, '') // remove query string + .split('/') + .at(-1) + if (!fileName) throw new Error('Cannot extract a filename') + return fileName +} diff --git a/packages/components/src/lib/utils.ts b/packages/components/src/lib/utils.ts index 2c64d891..3d0e9151 100644 --- a/packages/components/src/lib/utils.ts +++ b/packages/components/src/lib/utils.ts @@ -82,15 +82,6 @@ export function parseFileSize(headers: Headers): number | undefined { return contentLength ? Number(contentLength) : undefined } -export function getFileName(source: string): string { - const fileName = source - .replace(/\?.*$/, '') // remove query string - .split('/') - .at(-1) - if (!fileName) throw new Error('Cannot extract a filename') - return fileName -} - export const contentTypes: Record = { png: 'image/png', jpg: 'image/jpeg', diff --git a/packages/components/test/components/File.test.tsx b/packages/components/test/components/File.test.tsx index 985221ef..d310a8be 100644 --- a/packages/components/test/components/File.test.tsx +++ b/packages/components/test/components/File.test.tsx @@ -2,13 +2,9 @@ import { render } from '@testing-library/react' import { strict as assert } from 'assert' import React, { act } from 'react' import { describe, expect, it, vi } from 'vitest' -import File from '../../src/components/File.js' -import { createHttpFileSystem, createHyperparamFileSystem, getSource } from '../../src/lib/filesystem.js' -import { RoutesConfig } from '../../src/lib/routes.js' - -const hyparamFileSystem = createHyperparamFileSystem({ endpoint: 'http://localhost:3000' }) -const httpFileSystem = createHttpFileSystem() +import { File, RoutesConfig, getHttpSource, getHyperparamSource } from '../../src/index.js' +const endpoint = 'http://localhost:3000' const config: RoutesConfig = { routes: { @@ -22,7 +18,7 @@ global.fetch = vi.fn(() => Promise.resolve({ text: vi.fn() } as unknown as Respo describe('File Component', () => { it('renders a local file path', async () => { - const source = getSource('folder/subfolder/test.txt', hyparamFileSystem) + const source = getHyperparamSource('folder/subfolder/test.txt', { endpoint }) assert(source?.kind === 'file') const { getByText } = await act(() => render( @@ -37,7 +33,7 @@ describe('File Component', () => { it('renders a URL', async () => { const url = 'https://example.com/test.txt' - const source = getSource(url, httpFileSystem) + const source = getHttpSource(url) assert(source?.kind === 'file') const { getByText } = await act(() => render()) @@ -46,7 +42,7 @@ describe('File Component', () => { }) it('renders correct breadcrumbs for nested folders', async () => { - const source = getSource('folder1/folder2/folder3/test.txt', hyparamFileSystem) + const source = getHyperparamSource('folder1/folder2/folder3/test.txt', { endpoint }) assert(source?.kind === 'file') const { getAllByRole } = await act(() => render( diff --git a/packages/components/test/components/Folder.test.tsx b/packages/components/test/components/Folder.test.tsx index 1e3428b0..a8a354f4 100644 --- a/packages/components/test/components/Folder.test.tsx +++ b/packages/components/test/components/Folder.test.tsx @@ -2,11 +2,9 @@ import { render, waitFor } from '@testing-library/react' import { strict as assert } from 'assert' import React from 'react' import { describe, expect, it, test, vi } from 'vitest' -import Folder from '../../src/components/Folder.js' -import { HyperparamFileMetadata, createHyperparamFileSystem, getSource } from '../../src/lib/filesystem.js' -import { RoutesConfig } from '../../src/lib/routes.js' +import { Folder, HyperparamFileMetadata, RoutesConfig, getHyperparamSource } from '../../src/index.js' -const hyparamFileSystem = createHyperparamFileSystem({ endpoint: 'http://localhost:3000' }) +const endpoint = 'http://localhost:3000' const mockFiles: HyperparamFileMetadata[] = [ { key: 'folder1/', lastModified: '2023-01-01T00:00:00Z' }, { key: 'file1.txt', fileSize: 8196, lastModified: '2023-01-01T00:00:00Z' }, @@ -30,7 +28,7 @@ describe('Folder Component', () => { ok: true, } as Response) - const source = getSource(path, hyparamFileSystem) + const source = getHyperparamSource(path, { endpoint }) assert(source?.kind === 'directory') const { findByText, getByText } = render() @@ -52,7 +50,7 @@ describe('Folder Component', () => { ok: true, } as Response) - const source = getSource('', hyparamFileSystem) + const source = getHyperparamSource('', { endpoint }) assert(source?.kind === 'directory') const { container } = render() @@ -66,7 +64,7 @@ describe('Folder Component', () => { ok: false, } as Response) - const source = getSource('test-prefix/', hyparamFileSystem) + const source = getHyperparamSource('test-prefix/', { endpoint }) assert(source?.kind === 'directory') const { findByText, queryByText } = render() @@ -84,7 +82,7 @@ describe('Folder Component', () => { ok: true, } as Response) - const source = getSource('subdir1/subdir2/', hyparamFileSystem) + const source = getHyperparamSource('subdir1/subdir2/', { endpoint }) assert(source?.kind === 'directory') const { findByText, getByText } = render() diff --git a/packages/components/test/components/Layout.test.tsx b/packages/components/test/components/Layout.test.tsx index f7b49305..96efb2d6 100644 --- a/packages/components/test/components/Layout.test.tsx +++ b/packages/components/test/components/Layout.test.tsx @@ -1,8 +1,7 @@ import { render } from '@testing-library/react' import React from 'react' import { describe, expect, it, vi } from 'vitest' -import Layout, { Spinner } from '../../src/components/Layout.js' -import { cn } from '../../src/lib/utils.js' +import { Layout, Spinner, cn } from '../../src/index.js' vi.mock('next-auth/react', () => ({ signOut: vi.fn(), useSession: vi.fn() })) vi.mock('next/link', () => ({ default: vi.fn() })) diff --git a/packages/components/test/components/Markdown.test.tsx b/packages/components/test/components/Markdown.test.tsx index 17cc027f..22da3b98 100644 --- a/packages/components/test/components/Markdown.test.tsx +++ b/packages/components/test/components/Markdown.test.tsx @@ -1,7 +1,7 @@ import { render } from '@testing-library/react' import React from 'react' import { describe, expect, it } from 'vitest' -import Markdown from '../../src/components/Markdown.js' +import { Markdown } from '../../src/index.js' describe('Markdown', () => { it('renders plain text as a paragraph', () => { diff --git a/packages/components/test/components/viewers/ImageView.test.tsx b/packages/components/test/components/viewers/ImageView.test.tsx index 962df73b..aa26fccd 100644 --- a/packages/components/test/components/viewers/ImageView.test.tsx +++ b/packages/components/test/components/viewers/ImageView.test.tsx @@ -2,10 +2,7 @@ import { render } from '@testing-library/react' import { strict as assert } from 'assert' import React from 'react' import { describe, expect, it, vi } from 'vitest' -import ImageView from '../../../src/components/viewers/ImageView.js' -import { createHyperparamFileSystem, getSource } from '../../../src/lib/filesystem.js' - -const hyparamFileSystem = createHyperparamFileSystem({ endpoint: 'http://localhost:3000' }) +import { ImageView, getHyperparamSource } from '../../../src/index.js' global.fetch = vi.fn() @@ -17,7 +14,7 @@ describe('ImageView Component', () => { headers: new Map([['content-length', body.byteLength]]), } as unknown as Response) - const source = getSource('test.png', hyparamFileSystem) + const source = getHyperparamSource('test.png', { endpoint: 'http://localhost:3000' }) assert(source?.kind === 'file') const { findByRole, findByText } = render( diff --git a/packages/components/test/components/viewers/MarkdownView.test.tsx b/packages/components/test/components/viewers/MarkdownView.test.tsx index 4a1ad9b1..18742ef2 100644 --- a/packages/components/test/components/viewers/MarkdownView.test.tsx +++ b/packages/components/test/components/viewers/MarkdownView.test.tsx @@ -2,10 +2,7 @@ import { render } from '@testing-library/react' import { strict as assert } from 'assert' import React from 'react' import { describe, expect, it, vi } from 'vitest' -import MarkdownView from '../../../src/components/viewers/MarkdownView.js' -import { createHyperparamFileSystem, getSource } from '../../../src/lib/filesystem.js' - -const hyparamFileSystem = createHyperparamFileSystem({ endpoint: 'http://localhost:3000' }) +import { MarkdownView, getHyperparamSource } from '../../../src/index.js' global.fetch = vi.fn() @@ -17,7 +14,7 @@ describe('MarkdownView Component', () => { headers: new Map([['content-length', text.length]]), } as unknown as Response) - const source = getSource('test.md', hyparamFileSystem) + const source = getHyperparamSource('test.md', { endpoint: 'http://localhost:3000' }) assert(source?.kind === 'file') const { findByText } = render( diff --git a/packages/components/test/lib/utils.test.ts b/packages/components/test/lib/routes.test.ts similarity index 100% rename from packages/components/test/lib/utils.test.ts rename to packages/components/test/lib/routes.test.ts diff --git a/packages/components/test/lib/sources/httpSource.test.ts b/packages/components/test/lib/sources/httpSource.test.ts new file mode 100644 index 00000000..e9ff01a3 --- /dev/null +++ b/packages/components/test/lib/sources/httpSource.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it, test, vi } from 'vitest' +import { getHttpSource } from '../../../src/lib/sources/httpSource.js' + +global.fetch = vi.fn() + +describe('getHttpSource', () => { + test.for([ + 'http://example.com/test.txt', + 'https://example.com/test.txt', + 'http://weird', + ])('recognizes a URL', (sourceId: string) => { + const source = getHttpSource(sourceId) + expect(source?.kind).toBe('file') + }) + it('does not support encoded URLs', () => { + expect(getHttpSource('https%3A%2F%2Fhyperparam-public.s3.amazonaws.com%2Fbunnies.parquet')).toBeUndefined() + }) +}) diff --git a/packages/components/test/lib/filesystem.test.ts b/packages/components/test/lib/sources/hyperparamSource.test.ts similarity index 50% rename from packages/components/test/lib/filesystem.test.ts rename to packages/components/test/lib/sources/hyperparamSource.test.ts index dffdb4e0..3bb1a4e3 100644 --- a/packages/components/test/lib/filesystem.test.ts +++ b/packages/components/test/lib/sources/hyperparamSource.test.ts @@ -1,19 +1,17 @@ -import { describe, expect, it, test, vi } from 'vitest' -import { HyperparamFileMetadata, createHttpFileSystem, createHyperparamFileSystem } from '../../src/lib/filesystem.js' +import { assert, describe, expect, it, test, vi } from 'vitest' +import { HyperparamFileMetadata, getHyperparamSource } from '../../../src/lib/sources/hyperparamSource.js' global.fetch = vi.fn() -describe('createHyperparamFileSystem', () => { +describe('getHyperparamSource', () => { const endpoint = 'http://localhost:3000' - const fs = createHyperparamFileSystem({ endpoint }) test.for([ 'test.txt', 'no-extension', 'folder/subfolder/test.txt', ])('recognizes a local file path', (sourceId: string) => { - expect(fs.canParse(sourceId)).toBe(true) - expect(fs.getKind(sourceId)).toBe('file') + expect(getHyperparamSource(sourceId, { endpoint })?.kind).toBe('file') }) test.for([ @@ -21,22 +19,23 @@ describe('createHyperparamFileSystem', () => { 'folder1/', 'folder1/folder2/', ])('recognizes a folder', (sourceId: string) => { - expect(fs.canParse(sourceId)).toBe(true) - expect(fs.getKind(sourceId)).toBe('directory') + expect(getHyperparamSource(sourceId, { endpoint })?.kind).toBe('directory') }) test.for([ '/', '////', ])('does not support a heading slash', (sourceId: string) => { - expect(fs.canParse(sourceId)).toBe(false) + expect(getHyperparamSource(sourceId, { endpoint })).toBeUndefined() }) test.for([ 'test.txt', 'folder/subfolder/test.txt', ])('encodes the parameters in resolveUrl', (sourceId: string) => { - expect(fs.getResolveUrl(sourceId)).toBe(endpoint + '/api/store/get?key=' + encodeURIComponent(sourceId)) + const source = getHyperparamSource(sourceId, { endpoint }) + assert(source?.kind === 'file') + expect(source.resolveUrl).toBe(endpoint + '/api/store/get?key=' + encodeURIComponent(sourceId)) }) it('in listFiles, creates a full source by concatenating the file with the prefix', async () => { @@ -48,26 +47,12 @@ describe('createHyperparamFileSystem', () => { json: () => Promise.resolve(mockFiles), ok: true, } as Response) - const files = await fs.listFiles('folder0') + const source = getHyperparamSource('folder0/', { endpoint }) + assert(source?.kind === 'directory') + const files = await source.listFiles() expect(files).to.be.an('array').and.have.length(2) expect(files[0].sourceId).toBe('folder0/folder1/') expect(files[1].sourceId).toBe('folder0/file1.txt') }) }) - -describe('createHttpFileSystem', () => { - const fs = createHttpFileSystem() - - test.for([ - 'http://example.com/test.txt', - 'https://example.com/test.txt', - 'http://weird', - ])('recognizes a URL', (sourceId: string) => { - expect(fs.canParse(sourceId)).toBe(true) - expect(fs.getKind(sourceId)).toBe('file') - }) - it('does not support encoded URLs', () => { - expect(fs.canParse('https%3A%2F%2Fhyperparam-public.s3.amazonaws.com%2Fbunnies.parquet')).toBe(false) - }) -})