11// @vitest -environment jsdom
22import { describe , it , expect , afterEach , beforeEach } from "vitest" ;
3- import { render , cleanup , fireEvent , act } from "@testing-library/react" ;
3+ import { render , cleanup , fireEvent , act , within } from "@testing-library/react" ;
44import { StoredFontsTab } from "./StoredFontsTab" ;
55import { useLabelStore } from "../../store/labelStore" ;
66import { getAllFonts , loadFontBytes , removeFont } from "@zplab/core/lib/fontCache" ;
7+ import { fallbackTranslations as en } from "../../locales" ;
78
89beforeEach ( async ( ) => {
910 await loadFontBytes ( new Uint8Array ( [ 0 , 1 , 0 , 0 ] ) , "E:ARIAL.TTF" ) ;
@@ -12,9 +13,21 @@ beforeEach(async () => {
1213
1314afterEach ( ( ) => {
1415 cleanup ( ) ;
15- for ( const name of [ "E:ARIAL.TTF" , "R:ARIAL.TTF" , "E:NEW.TTF" , "E:OLD.BIN" , "E:MYLOGO.TTF" , " R:GONE.TTF", "E:OTHER.TTF" ] ) removeFont ( name ) ;
16+ for ( const name of [ "E:ARIAL.TTF" , "R:ARIAL.TTF" , "E:NEW.TTF" , "E:OLD.BIN" , "R:GONE.TTF" , "E:OTHER.TTF" ] ) removeFont ( name ) ;
1617} ) ;
1718
19+ const addFile = async ( r : ReturnType < typeof render > , open : string , file : File ) => {
20+ fireEvent . click ( r . getByText ( open ) ) ;
21+ // The dialog is portaled, so its hidden input sits outside `container`.
22+ const input = r . baseElement . querySelector ( 'input[type="file"]' ) as HTMLInputElement ;
23+ await act ( async ( ) => {
24+ fireEvent . change ( input , { target : { files : [ file ] } } ) ;
25+ } ) ;
26+ await act ( async ( ) => {
27+ fireEvent . click ( within ( r . getByRole ( "dialog" ) ) . getByRole ( "button" , { name : "Add" } ) ) ;
28+ } ) ;
29+ } ;
30+
1831describe ( "StoredFontsTab" , ( ) => {
1932 it ( "lists a cached font and adds it to the setup script" , ( ) => {
2033 const { getByLabelText, getByTitle } = render ( < StoredFontsTab /> ) ;
@@ -99,43 +112,38 @@ describe("StoredFontsTab", () => {
99112 expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toBeUndefined ( ) ;
100113 } ) ;
101114
102- it ( "repairs a missing entry under its own path instead of the default drive " , async ( ) => {
115+ it ( "repairs a missing entry under its own path, whatever the picked file is called " , async ( ) => {
103116 act ( ( ) => useLabelStore . setState ( { printerProfile : { setupFonts : [ { path : "R:GONE.TTF" } ] } } ) ) ;
104- const { getByText, getByLabelText, queryByText } = render ( < StoredFontsTab /> ) ;
105- act ( ( ) => {
106- fireEvent . click ( getByText ( "Upload the file" ) ) ;
107- } ) ;
108- await act ( async ( ) => {
109- fireEvent . change ( getByLabelText ( / U p l o a d f o n t / ) , { target : { files : [ new File ( [ "x" ] , "other.ttf" ) ] } } ) ;
110- } ) ;
117+ const r = render ( < StoredFontsTab /> ) ;
118+ await addFile ( r , "Upload the file" , new File ( [ "x" ] , "other.ttf" ) ) ;
111119 expect ( getAllFonts ( ) . map ( ( f ) => f . name ) ) . toContain ( "R:GONE.TTF" ) ;
112- expect ( queryByText ( / F o n t m i s s i n g / ) ) . toBeNull ( ) ;
120+ expect ( r . queryByText ( / F o n t m i s s i n g / ) ) . toBeNull ( ) ;
113121 expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toEqual ( [ { path : "R:GONE.TTF" } ] ) ;
114122 } ) ;
115123
124+ it ( "keeps an imported upload whose only copy is the profile, rather than replacing it" , async ( ) => {
125+ act ( ( ) => useLabelStore . setState ( { printerProfile : { setupFonts : [ { path : "E:OTHER.TTF" , download : "~DYE:OTHER.TTF,A,T,4,,00010000" } ] } } ) ) ;
126+ const r = render ( < StoredFontsTab /> ) ;
127+ await addFile ( r , "Upload font…" , new File ( [ "x" ] , "other.ttf" ) ) ;
128+ expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toEqual ( [ { path : "E:OTHER.TTF" , download : "~DYE:OTHER.TTF,A,T,4,,00010000" } ] ) ;
129+ // The cache must stay out of it too, or the canvas would draw bytes the setup script never sends.
130+ expect ( getAllFonts ( ) . map ( ( f ) => f . name ) ) . not . toContain ( "E:OTHER.TTF" ) ;
131+ expect ( r . getByRole ( "alert" ) . textContent ) . toBe ( en . printerSettings . fonts . replayedNameTaken ) ;
132+ } ) ;
133+
116134 it ( "uploads a font file into the cache and the setup script in one step" , async ( ) => {
117- const { getByLabelText } = render ( < StoredFontsTab /> ) ;
118- await act ( async ( ) => {
119- fireEvent . change ( getByLabelText ( / U p l o a d f o n t / ) , { target : { files : [ new File ( [ "x" ] , "new.ttf" ) ] } } ) ;
120- } ) ;
135+ const r = render ( < StoredFontsTab /> ) ;
136+ await addFile ( r , "Upload font…" , new File ( [ "x" ] , "new.ttf" ) ) ;
121137 expect ( getAllFonts ( ) . map ( ( f ) => f . name ) ) . toContain ( "E:NEW.TTF" ) ;
122138 expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toEqual ( [ { path : "E:NEW.TTF" } ] ) ;
123139 } ) ;
124140
125- it ( "gives a picked file a printer name the ~DY operand can carry" , async ( ) => {
126- const { getByLabelText } = render ( < StoredFontsTab /> ) ;
127- await act ( async ( ) => {
128- fireEvent . change ( getByLabelText ( / U p l o a d f o n t / ) , { target : { files : [ new File ( [ "x" ] , "my logo.ttf" ) ] } } ) ;
129- } ) ;
130- expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toEqual ( [ { path : "E:MYLOGO.TTF" } ] ) ;
131- } ) ;
132-
133141 it ( "locks every profile control while the editor is frozen, instead of snapping back" , ( ) => {
134142 act ( ( ) => useLabelStore . setState ( { sourceEdit : { status : "editing" , draft : "^XA^XZ" , baseline : "^XA^XZ" , session : 1 } } ) ) ;
135- const { getByLabelText, getByRole } = render ( < StoredFontsTab /> ) ;
143+ const { getByLabelText, getByRole, getByText } = render ( < StoredFontsTab /> ) ;
136144 const toggle = getByLabelText ( / S e n d a t s e t u p / ) as HTMLInputElement ;
137145 expect ( toggle . disabled ) . toBe ( true ) ;
138- const upload = ( getByLabelText ( / U p l o a d f o n t / ) as HTMLInputElement ) . closest ( "div" ) ?. querySelector ( "button ") as HTMLButtonElement ;
146+ const upload = getByText ( " Upload font… ") as HTMLButtonElement ;
139147 expect ( upload . disabled ) . toBe ( true ) ;
140148 // A disabled control swallows its own events, so the reason sits on the wrapper.
141149 act ( ( ) => {
@@ -145,21 +153,4 @@ describe("StoredFontsTab", () => {
145153 act ( ( ) => useLabelStore . setState ( { sourceEdit : { status : "off" } } ) ) ;
146154 } ) ;
147155
148- it ( "refuses a second file that folds onto a cached printer name with different bytes" , async ( ) => {
149- const { getByLabelText, getByText } = render ( < StoredFontsTab /> ) ;
150- await act ( async ( ) => {
151- fireEvent . change ( getByLabelText ( / U p l o a d f o n t / ) , { target : { files : [ new File ( [ "other bytes" ] , "arial.ttf" ) ] } } ) ;
152- } ) ;
153- expect ( getAllFonts ( ) ) . toHaveLength ( 1 ) ;
154- expect ( getByText ( / d i f f e r e n t p r i n t e r f i l e n a m e / ) ) . toBeTruthy ( ) ;
155- expect ( useLabelStore . getState ( ) . printerProfile . setupFonts ) . toBeUndefined ( ) ;
156- } ) ;
157-
158- it ( "names a failed upload" , async ( ) => {
159- const { getByLabelText, getByText } = render ( < StoredFontsTab /> ) ;
160- await act ( async ( ) => {
161- fireEvent . change ( getByLabelText ( / U p l o a d f o n t / ) , { target : { files : [ new File ( [ "x" ] , "bad.txt" ) ] } } ) ;
162- } ) ;
163- expect ( getByText ( / C o u l d n o t l o a d t h e f o n t f i l e / ) ) . toBeTruthy ( ) ;
164- } ) ;
165156} ) ;
0 commit comments