@@ -3,6 +3,8 @@ import { JSDOM } from 'jsdom'
33import * as RdfLib from 'rdflib'
44import { lightColorHash , notepad } from '../../src/pad'
55import { log } from '../../src/debug'
6+ import ns from '../../src/ns'
7+ import { solidLogicSingleton } from 'solid-logic'
68
79silenceDebugMessages ( )
810const window = new JSDOM ( '<!DOCTYPE html><p>Hello world</p>' ) . window
@@ -25,6 +27,39 @@ describe('lightColorHash', () => {
2527} )
2628
2729describe ( 'notepad' , ( ) => {
30+ const store : any = solidLogicSingleton . store
31+ const PAD = RdfLib . Namespace ( 'http://www.w3.org/ns/pim/pad#' )
32+ let originalUpdater : any
33+
34+ function setupExistingPadFixture ( ) {
35+ const id = Date . now ( ) . toString ( ) + Math . random ( ) . toString ( ) . slice ( 2 )
36+ const padDoc = new RdfLib . NamedNode ( `https://pad.example/${ id } .ttl` )
37+ const subject = new RdfLib . NamedNode ( `https://pad.example/${ id } .ttl#pad` )
38+ const chunk = new RdfLib . NamedNode ( `https://pad.example/${ id } .ttl#line1` )
39+ const me = new RdfLib . NamedNode ( 'https://sharonstrats.inrupt.net/profile/card#me' )
40+
41+ store . add ( subject , PAD ( 'next' ) , chunk , padDoc )
42+ store . add ( chunk , PAD ( 'next' ) , subject , padDoc )
43+ store . add ( chunk , ns . sioc ( 'content' ) , 'initial' , padDoc )
44+ store . add ( chunk , ns . dc ( 'author' ) , me , padDoc )
45+
46+ const table = notepad ( dom , padDoc , subject , me , { exists : true } )
47+ const part = table . querySelector ( 'input' ) as any
48+ if ( ! part ) {
49+ throw new Error ( 'Expected notepad to render an input part' )
50+ }
51+ return { padDoc, subject, chunk, me, part }
52+ }
53+
54+ beforeEach ( ( ) => {
55+ originalUpdater = store . updater
56+ } )
57+
58+ afterEach ( ( ) => {
59+ store . updater = originalUpdater
60+ jest . useRealTimers ( )
61+ } )
62+
2863 it ( 'to be exposed by the Public API' , ( ) => {
2964 expect ( notepad ) . toBe ( notepad )
3065 } )
@@ -75,4 +110,84 @@ describe('notepad', () => {
75110 expect ( notepad ( dom , padDoc , subject , me , options )
76111 ) . resolves . toBe ( { } )
77112 } )
113+
114+ it ( 'debounces rapid input and sends one update after pause' , ( ) => {
115+ jest . useFakeTimers ( )
116+
117+ const update = jest . fn ( ( _del , _ins , cb ) => cb ( null , true , '' , { status : 200 } ) )
118+ store . updater = {
119+ update,
120+ requestDownstreamAction : jest . fn ( ) ,
121+ reload : jest . fn ( ) ,
122+ store
123+ }
124+
125+ const { padDoc, subject, chunk, part } = setupExistingPadFixture ( )
126+
127+ expect ( ( ) => {
128+ part . value = 'a'
129+ part . dispatchEvent ( new window . Event ( 'input' , { bubbles : true } ) )
130+ part . value = 'ab'
131+ part . dispatchEvent ( new window . Event ( 'input' , { bubbles : true } ) )
132+ part . value = 'abc'
133+ part . dispatchEvent ( new window . Event ( 'input' , { bubbles : true } ) )
134+ } ) . not . toThrow ( )
135+
136+ expect ( update ) . toHaveBeenCalledTimes ( 0 )
137+ jest . advanceTimersByTime ( 399 )
138+ expect ( update ) . toHaveBeenCalledTimes ( 0 )
139+ jest . advanceTimersByTime ( 1 )
140+ expect ( update ) . toHaveBeenCalledTimes ( 1 )
141+
142+ // Cleanup this test fixture's statements.
143+ store . removeMatches ( subject , null , null , padDoc )
144+ store . removeMatches ( chunk , null , null , padDoc )
145+ store . removeMatches ( null , null , chunk , padDoc )
146+ } )
147+
148+ it ( 'retries on transient 503 and keeps state/lastSent coherent' , ( ) => {
149+ jest . useFakeTimers ( )
150+
151+ let callCount = 0
152+ const update = jest . fn ( ( _del , _ins , cb ) => {
153+ callCount += 1
154+ if ( callCount === 1 ) {
155+ cb ( null , false , 'transient' , { status : 503 } )
156+ } else {
157+ cb ( null , true , '' , { status : 200 } )
158+ }
159+ } )
160+
161+ store . updater = {
162+ update,
163+ requestDownstreamAction : jest . fn ( ) ,
164+ reload : jest . fn ( ) ,
165+ store
166+ }
167+
168+ const { padDoc, subject, chunk, part } = setupExistingPadFixture ( )
169+ part . value = 'queued text'
170+
171+ expect ( ( ) => {
172+ part . dispatchEvent ( new window . Event ( 'input' , { bubbles : true } ) )
173+ jest . advanceTimersByTime ( 400 ) // debounce fires first PATCH
174+ } ) . not . toThrow ( )
175+
176+ expect ( update ) . toHaveBeenCalledTimes ( 1 )
177+ expect ( part . state ) . toBe ( 0 )
178+ expect ( part . lastSent ) . toBeUndefined ( )
179+
180+ jest . advanceTimersByTime ( 1999 )
181+ expect ( update ) . toHaveBeenCalledTimes ( 1 )
182+ jest . advanceTimersByTime ( 1 )
183+
184+ expect ( update ) . toHaveBeenCalledTimes ( 2 )
185+ expect ( part . state ) . toBe ( 0 )
186+ expect ( part . lastSent ) . toBe ( 'queued text' )
187+
188+ // Cleanup this test fixture's statements.
189+ store . removeMatches ( subject , null , null , padDoc )
190+ store . removeMatches ( chunk , null , null , padDoc )
191+ store . removeMatches ( null , null , chunk , padDoc )
192+ } )
78193} )
0 commit comments