Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite3b7f0a

Browse files
committed
test: Enhance tests for useSuspenseIndexedDBState with BroadcastChannel handling
- Added tests to verify correct handling of BroadcastChannel messages in the useSuspenseIndexedDBState hook.- Implemented a MockBroadcastChannel for simulating message delivery across tabs.- Ensured that the hook updates state correctly based on broadcast messages for both SET and DELETE actions.- Improved test coverage for scenarios involving different keys and databases.
1 parent165538d commite3b7f0a

File tree

3 files changed

+316
-27
lines changed

3 files changed

+316
-27
lines changed

‎.changeset/stupid-chairs-stare.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"rooks":patch
3+
---
4+
5+
cross tab fix

‎packages/rooks/src/__tests__/useSuspenseIndexedDBState.spec.tsx‎

Lines changed: 289 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,28 @@ if (typeof global.BroadcastChannel === "undefined") {
2626
name:string;
2727
onmessage:((event:MessageEvent)=>void)|null=null;
2828
onmessageerror:((event:MessageEvent)=>void)|null=null;
29-
29+
3030
constructor(name:string){
3131
this.name=name;
3232
}
33-
33+
3434
postMessage(_message:unknown){
3535
// Mock implementation - no cross-tab sync in tests
3636
}
37-
37+
3838
addEventListener(_type:string,_listener:EventListener){
3939
// Mock implementation
4040
}
41-
41+
4242
removeEventListener(_type:string,_listener:EventListener){
4343
// Mock implementation
4444
}
45-
45+
4646
dispatchEvent(_event:Event):boolean{
4747
// Mock implementation
4848
returntrue;
4949
}
50-
50+
5151
close(){
5252
// Mock implementation
5353
}
@@ -56,8 +56,8 @@ if (typeof global.BroadcastChannel === "undefined") {
5656

5757
// Mock console methods to reduce test noise
5858
beforeAll(()=>{
59-
jest.spyOn(console,'error').mockImplementation(()=>{});
60-
jest.spyOn(console,'warn').mockImplementation(()=>{});
59+
jest.spyOn(console,'error').mockImplementation(()=>{});
60+
jest.spyOn(console,'warn').mockImplementation(()=>{});
6161
});
6262

6363
afterAll(()=>{
@@ -181,10 +181,10 @@ function ObjectTestComponent({ storageKey }: { storageKey: string }) {
181181
}
182182

183183
// Wrapper with Suspense and Error Boundary
184-
functionSuspenseWrapper({
185-
children,
186-
fallback="Loading..."
187-
}:{
184+
functionSuspenseWrapper({
185+
children,
186+
fallback="Loading..."
187+
}:{
188188
children:React.ReactNode;
189189
fallback?:string;
190190
}){
@@ -677,4 +677,281 @@ describe("useSuspenseIndexedDBState", () => {
677677
expect(screen.getByTestId("user-name").textContent).toBe("Updated User");
678678
expect(screen.getByTestId("items-count").textContent).toBe("3");
679679
});
680+
681+
it("should handle broadcast channel messages correctly",async()=>{
682+
expect.hasAssertions();
683+
684+
// Create a working BroadcastChannel implementation for testing
685+
constchannels:Map<string,MockBroadcastChannel[]>=newMap();
686+
687+
classMockBroadcastChannelimplementsBroadcastChannel{
688+
name:string;
689+
onmessage:((event:MessageEvent)=>void)|null=null;
690+
onmessageerror:((event:MessageEvent)=>void)|null=null;
691+
privatelisteners:EventListener[]=[];
692+
693+
constructor(name:string){
694+
this.name=name;
695+
if(!channels.has(name)){
696+
channels.set(name,[]);
697+
}
698+
channels.get(name)!.push(this);
699+
}
700+
701+
postMessage(message:unknown){
702+
constchannelInstances=channels.get(this.name)||[];
703+
// Simulate async message delivery
704+
setTimeout(()=>{
705+
channelInstances.forEach(instance=>{
706+
if(instance!==this){// Don't send to self
707+
constevent=newMessageEvent('message',{data:message});
708+
instance.listeners.forEach(listener=>{
709+
listener(event);
710+
});
711+
if(instance.onmessage){
712+
instance.onmessage(event);
713+
}
714+
}
715+
});
716+
},0);
717+
}
718+
719+
addEventListener(type:string,listener:EventListener){
720+
if(type==='message'){
721+
this.listeners.push(listener);
722+
}
723+
}
724+
725+
removeEventListener(type:string,listener:EventListener){
726+
if(type==='message'){
727+
constindex=this.listeners.indexOf(listener);
728+
if(index>-1){
729+
this.listeners.splice(index,1);
730+
}
731+
}
732+
}
733+
734+
dispatchEvent(_event:Event):boolean{
735+
returntrue;
736+
}
737+
738+
close(){
739+
constchannelInstances=channels.get(this.name);
740+
if(channelInstances){
741+
constindex=channelInstances.indexOf(this);
742+
if(index>-1){
743+
channelInstances.splice(index,1);
744+
}
745+
}
746+
}
747+
}
748+
749+
// Temporarily replace the global BroadcastChannel
750+
constoriginalBroadcastChannel=global.BroadcastChannel;
751+
global.BroadcastChannel=MockBroadcastChannelasany;
752+
753+
lettestChannel:MockBroadcastChannel|null=null;
754+
testChannel=newMockBroadcastChannel("rooks-indexeddb-rooks-db-state");
755+
756+
constTestBroadcastComponent=()=>{
757+
const[value,{ setItem}]=useSuspenseIndexedDBState(
758+
"broadcast-test",
759+
(current)=>current||"initial"
760+
);
761+
762+
return(
763+
<div>
764+
<divdata-testid="broadcast-value">{JSON.stringify(value)}</div>
765+
<button
766+
data-testid="set-broadcast-value"
767+
onClick={()=>setItem("local-update")}
768+
>
769+
Set Value
770+
</button>
771+
</div>
772+
);
773+
};
774+
775+
render(
776+
<SuspenseWrapper>
777+
<TestBroadcastComponent/>
778+
</SuspenseWrapper>
779+
);
780+
781+
// Wait for component to load
782+
awaitwaitFor(()=>{
783+
expect(screen.getByTestId("broadcast-value")).toBeInTheDocument();
784+
});
785+
786+
expect(screen.getByTestId("broadcast-value").textContent).toBe('"initial"');
787+
788+
// Simulate broadcast message from another tab
789+
if(testChannel){
790+
awaitact(async()=>{
791+
testChannel.postMessage({
792+
type:'SET',
793+
key:'broadcast-test',
794+
value:'broadcast-update',
795+
dbName:'rooks-db',
796+
storeName:'state'
797+
});
798+
awaitnewPromise(resolve=>setTimeout(resolve,100));
799+
});
800+
801+
// Value should be updated from broadcast message
802+
expect(screen.getByTestId("broadcast-value").textContent).toBe('"broadcast-update"');
803+
804+
// Test DELETE broadcast message
805+
awaitact(async()=>{
806+
testChannel.postMessage({
807+
type:'DELETE',
808+
key:'broadcast-test',
809+
dbName:'rooks-db',
810+
storeName:'state'
811+
});
812+
awaitnewPromise(resolve=>setTimeout(resolve,100));
813+
});
814+
815+
// Value should be reset to initial value
816+
expect(screen.getByTestId("broadcast-value").textContent).toBe('"initial"');
817+
818+
testChannel.close();
819+
}
820+
821+
// Restore original BroadcastChannel
822+
global.BroadcastChannel=originalBroadcastChannel;
823+
});
824+
825+
it("should not respond to broadcast messages for different keys/databases",async()=>{
826+
expect.hasAssertions();
827+
828+
// Create a working BroadcastChannel implementation for testing
829+
constchannels:Map<string,MockBroadcastChannel[]>=newMap();
830+
831+
classMockBroadcastChannelimplementsBroadcastChannel{
832+
name:string;
833+
onmessage:((event:MessageEvent)=>void)|null=null;
834+
onmessageerror:((event:MessageEvent)=>void)|null=null;
835+
privatelisteners:EventListener[]=[];
836+
837+
constructor(name:string){
838+
this.name=name;
839+
if(!channels.has(name)){
840+
channels.set(name,[]);
841+
}
842+
channels.get(name)!.push(this);
843+
}
844+
845+
postMessage(message:unknown){
846+
constchannelInstances=channels.get(this.name)||[];
847+
setTimeout(()=>{
848+
channelInstances.forEach(instance=>{
849+
if(instance!==this){
850+
constevent=newMessageEvent('message',{data:message});
851+
instance.listeners.forEach(listener=>{
852+
listener(event);
853+
});
854+
if(instance.onmessage){
855+
instance.onmessage(event);
856+
}
857+
}
858+
});
859+
},0);
860+
}
861+
862+
addEventListener(type:string,listener:EventListener){
863+
if(type==='message'){
864+
this.listeners.push(listener);
865+
}
866+
}
867+
868+
removeEventListener(type:string,listener:EventListener){
869+
if(type==='message'){
870+
constindex=this.listeners.indexOf(listener);
871+
if(index>-1){
872+
this.listeners.splice(index,1);
873+
}
874+
}
875+
}
876+
877+
dispatchEvent(_event:Event):boolean{
878+
returntrue;
879+
}
880+
881+
close(){
882+
constchannelInstances=channels.get(this.name);
883+
if(channelInstances){
884+
constindex=channelInstances.indexOf(this);
885+
if(index>-1){
886+
channelInstances.splice(index,1);
887+
}
888+
}
889+
}
890+
}
891+
892+
// Temporarily replace the global BroadcastChannel
893+
constoriginalBroadcastChannel=global.BroadcastChannel;
894+
global.BroadcastChannel=MockBroadcastChannelasany;
895+
896+
lettestChannel:MockBroadcastChannel|null=null;
897+
testChannel=newMockBroadcastChannel("rooks-indexeddb-rooks-db-state");
898+
899+
constTestBroadcastComponent=()=>{
900+
const[value]=useSuspenseIndexedDBState(
901+
"specific-key",
902+
(current)=>current||"original"
903+
);
904+
905+
return<divdata-testid="specific-value">{JSON.stringify(value)}</div>;
906+
};
907+
908+
render(
909+
<SuspenseWrapper>
910+
<TestBroadcastComponent/>
911+
</SuspenseWrapper>
912+
);
913+
914+
awaitwaitFor(()=>{
915+
expect(screen.getByTestId("specific-value")).toBeInTheDocument();
916+
});
917+
918+
expect(screen.getByTestId("specific-value").textContent).toBe('"original"');
919+
920+
if(testChannel){
921+
// Send message for different key - should not affect our component
922+
awaitact(async()=>{
923+
testChannel.postMessage({
924+
type:'SET',
925+
key:'different-key',
926+
value:'should-not-update',
927+
dbName:'rooks-db',
928+
storeName:'state'
929+
});
930+
awaitnewPromise(resolve=>setTimeout(resolve,100));
931+
});
932+
933+
// Value should remain unchanged
934+
expect(screen.getByTestId("specific-value").textContent).toBe('"original"');
935+
936+
// Send message for different database - should not affect our component
937+
awaitact(async()=>{
938+
testChannel.postMessage({
939+
type:'SET',
940+
key:'specific-key',
941+
value:'should-not-update',
942+
dbName:'different-db',
943+
storeName:'state'
944+
});
945+
awaitnewPromise(resolve=>setTimeout(resolve,100));
946+
});
947+
948+
// Value should remain unchanged
949+
expect(screen.getByTestId("specific-value").textContent).toBe('"original"');
950+
951+
testChannel.close();
952+
}
953+
954+
// Restore original BroadcastChannel
955+
global.BroadcastChannel=originalBroadcastChannel;
956+
});
680957
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp