@@ -13,14 +13,16 @@ import { SystemConfig } from "@App/pkg/config/config";
1313import EventEmitter from "eventemitter3" ;
1414import type { ValueService } from "./value" ;
1515import type { ResourceService } from "./resource" ;
16- import type { TDeleteScript , TInstallScript } from "@App/app/service/queue" ;
16+ import type { TDeleteScript , TInstallScript , TSortedScript } from "@App/app/service/queue" ;
17+ import { CLOUD_SYNC_QUEUE_KEY } from "@App/app/service/queue" ;
1718import { createMockOPFS } from "@App/app/repo/test-helpers" ;
1819import type { Group } from "@Packages/message/server" ;
1920import type { IMessageQueue } from "@Packages/message/message_queue" ;
2021import type { MessageSend } from "@Packages/message/types" ;
2122import { ScriptClient } from "./client" ;
2223import { SELF_METADATA_ONLY_RUN_ON_URL } from "@App/app/repo/metadata" ;
2324import { BatchUpdateListActionCode } from "./types" ;
25+ import { stackAsyncTask } from "@App/pkg/utils/async_queue" ;
2426
2527initTestEnv ( ) ;
2628
@@ -108,6 +110,140 @@ describe("ScriptService.purgeScripts —— 彻底删除", () => {
108110 } ) ;
109111} ) ;
110112
113+ describe ( "ScriptService.sortScript" , ( ) => {
114+ beforeEach ( async ( ) => {
115+ await resetActiveScriptData ( ) ;
116+ } ) ;
117+
118+ it ( "拖动排序只更新位置变化的脚本并发布排序更新时间" , async ( ) => {
119+ const { service, scriptDAO, mq } = buildService ( ) ;
120+ await scriptDAO . save ( makeScript ( { uuid : "first" , sort : 0 , updatetime : 100 } ) ) ;
121+ await scriptDAO . save ( makeScript ( { uuid : "second" , sort : 1 , updatetime : 1_000 } ) ) ;
122+ const sorted : TSortedScript [ ] [ ] = [ ] ;
123+ mq . subscribe < TSortedScript [ ] > ( "sortedScripts" , ( value ) => void sorted . push ( value ) ) ;
124+ const now = vi . spyOn ( Date , "now" ) . mockReturnValue ( 1_000 ) ;
125+
126+ try {
127+ await service . sortScript ( { before : [ "first" , "second" ] , after : [ "second" , "first" ] } ) ;
128+ } finally {
129+ now . mockRestore ( ) ;
130+ }
131+
132+ await expect ( scriptDAO . get ( "first" ) ) . resolves . toMatchObject ( { sort : 1 , updatetime : 100 } ) ;
133+ await expect ( scriptDAO . get ( "second" ) ) . resolves . toMatchObject ( { sort : 0 , updatetime : 1_000 } ) ;
134+ expect ( sorted [ 0 ] ) . toEqual ( [
135+ { uuid : "second" , sort : 0 , sortUpdatetime : 1_000 } ,
136+ { uuid : "first" , sort : 1 , sortUpdatetime : 1_000 } ,
137+ ] ) ;
138+ } ) ;
139+
140+ it ( "拖动部分列表时不写入位置未变化的脚本" , async ( ) => {
141+ const { service, scriptDAO } = buildService ( ) ;
142+ for ( let index = 0 ; index < 4 ; index += 1 ) {
143+ await scriptDAO . save ( makeScript ( { uuid : `script-${ index } ` , sort : index , updatetime : 100 + index } ) ) ;
144+ }
145+ const now = vi . spyOn ( Date , "now" ) . mockReturnValue ( 1_000 ) ;
146+
147+ try {
148+ await service . sortScript ( {
149+ before : [ "script-0" , "script-1" , "script-2" , "script-3" ] ,
150+ after : [ "script-1" , "script-0" , "script-2" , "script-3" ] ,
151+ } ) ;
152+ } finally {
153+ now . mockRestore ( ) ;
154+ }
155+
156+ await expect ( scriptDAO . get ( "script-1" ) ) . resolves . toMatchObject ( { sort : 0 , updatetime : 101 } ) ;
157+ await expect ( scriptDAO . get ( "script-0" ) ) . resolves . toMatchObject ( { sort : 1 , updatetime : 100 } ) ;
158+ await expect ( scriptDAO . get ( "script-2" ) ) . resolves . toMatchObject ( { sort : 2 , updatetime : 102 } ) ;
159+ await expect ( scriptDAO . get ( "script-3" ) ) . resolves . toMatchObject ( { sort : 3 , updatetime : 103 } ) ;
160+ } ) ;
161+
162+ it ( "全量同步进行时排序 mutation 不应穿插执行" , async ( ) => {
163+ const { service, scriptDAO } = buildService ( ) ;
164+ await scriptDAO . save ( makeScript ( { uuid : "first" , sort : 0 } ) ) ;
165+ await scriptDAO . save ( makeScript ( { uuid : "second" , sort : 1 } ) ) ;
166+ const allSpy = vi . spyOn ( scriptDAO , "all" ) ;
167+ let releaseSync ! : ( ) => void ;
168+ const syncGate = new Promise < void > ( ( resolve ) => {
169+ releaseSync = resolve ;
170+ } ) ;
171+ const syncPromise = stackAsyncTask ( CLOUD_SYNC_QUEUE_KEY , ( ) => syncGate ) ;
172+ let sortResolved = false ;
173+ const sortPromise = service . sortScript ( { before : [ "first" , "second" ] , after : [ "second" , "first" ] } ) . then ( ( ) => {
174+ sortResolved = true ;
175+ } ) ;
176+
177+ await Promise . resolve ( ) ;
178+ expect ( allSpy ) . not . toHaveBeenCalled ( ) ;
179+ expect ( sortResolved ) . toBe ( false ) ;
180+
181+ releaseSync ( ) ;
182+ await Promise . all ( [ syncPromise , sortPromise ] ) ;
183+ expect ( allSpy ) . toHaveBeenCalledTimes ( 1 ) ;
184+ await expect ( scriptDAO . get ( "second" ) ) . resolves . toMatchObject ( { sort : 0 } ) ;
185+ } ) ;
186+ } ) ;
187+
188+ describe ( "ScriptService.getAllScripts" , ( ) => {
189+ beforeEach ( async ( ) => {
190+ await resetActiveScriptData ( ) ;
191+ } ) ;
192+
193+ it ( "规范化旧排序时只登记位置变化的脚本" , async ( ) => {
194+ const { service, scriptDAO, mq } = buildService ( ) ;
195+ await scriptDAO . save ( makeScript ( { uuid : "first" , sort : - 1 , updatetime : 100 } ) ) ;
196+ await scriptDAO . save ( makeScript ( { uuid : "second" , sort : 1 , updatetime : 200 } ) ) ;
197+ const sorted : TSortedScript [ ] [ ] = [ ] ;
198+ mq . subscribe < TSortedScript [ ] > ( "sortedScripts" , ( value ) => void sorted . push ( value ) ) ;
199+ const now = vi . spyOn ( Date , "now" ) . mockReturnValue ( 1_000 ) ;
200+
201+ try {
202+ await service . getAllScripts ( ) ;
203+ } finally {
204+ now . mockRestore ( ) ;
205+ }
206+
207+ await expect ( scriptDAO . get ( "first" ) ) . resolves . toMatchObject ( { sort : 0 , updatetime : 100 } ) ;
208+ await expect ( scriptDAO . get ( "second" ) ) . resolves . toMatchObject ( { sort : 1 , updatetime : 200 } ) ;
209+ expect ( sorted [ 0 ] ) . toEqual ( [
210+ { uuid : "first" , sort : 0 , sortUpdatetime : 1_000 } ,
211+ { uuid : "second" , sort : 1 } ,
212+ ] ) ;
213+ } ) ;
214+ } ) ;
215+
216+ describe ( "ScriptService.pinToTop" , ( ) => {
217+ beforeEach ( async ( ) => {
218+ await resetActiveScriptData ( ) ;
219+ } ) ;
220+
221+ it ( "置顶只更新位置变化的脚本并发布同一个排序更新时间" , async ( ) => {
222+ const { service, scriptDAO, mq } = buildService ( ) ;
223+ await scriptDAO . save ( makeScript ( { uuid : "first" , sort : 0 , updatetime : 100 } ) ) ;
224+ await scriptDAO . save ( makeScript ( { uuid : "second" , sort : 1 , updatetime : 200 } ) ) ;
225+ await scriptDAO . save ( makeScript ( { uuid : "third" , sort : 2 , updatetime : 300 } ) ) ;
226+ const sorted : TSortedScript [ ] [ ] = [ ] ;
227+ mq . subscribe < TSortedScript [ ] > ( "sortedScripts" , ( value ) => void sorted . push ( value ) ) ;
228+ const now = vi . spyOn ( Date , "now" ) . mockReturnValue ( 1_000 ) ;
229+
230+ try {
231+ await service . pinToTop ( [ "second" ] ) ;
232+ } finally {
233+ now . mockRestore ( ) ;
234+ }
235+
236+ await expect ( scriptDAO . get ( "first" ) ) . resolves . toMatchObject ( { sort : 1 , updatetime : 100 } ) ;
237+ await expect ( scriptDAO . get ( "second" ) ) . resolves . toMatchObject ( { sort : 0 , updatetime : 200 } ) ;
238+ await expect ( scriptDAO . get ( "third" ) ) . resolves . toMatchObject ( { sort : 2 , updatetime : 300 } ) ;
239+ expect ( sorted [ 0 ] ) . toEqual ( [
240+ { uuid : "second" , sort : 0 , sortUpdatetime : 1_000 } ,
241+ { uuid : "first" , sort : 1 , sortUpdatetime : 1_000 } ,
242+ { uuid : "third" , sort : 2 } ,
243+ ] ) ;
244+ } ) ;
245+ } ) ;
246+
111247describe ( "ScriptService.deleteScripts —— 进回收站" , ( ) => {
112248 beforeEach ( async ( ) => {
113249 await resetActiveScriptData ( ) ;
0 commit comments