oh-my-opencode-dashboard 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { toDashboardPayload } from "./App"
3
+
4
+ describe('toDashboardPayload', () => {
5
+ it('should preserve planProgress.steps from server JSON', () => {
6
+ // #given: server JSON with planProgress.steps
7
+ const serverJson = {
8
+ mainSession: {
9
+ agent: "sisyphus",
10
+ currentTool: "dashboard_start",
11
+ currentModel: "anthropic/claude-opus-4-5",
12
+ lastUpdatedLabel: "just now",
13
+ session: "test-session",
14
+ statusPill: "busy",
15
+ },
16
+ planProgress: {
17
+ name: "test-plan",
18
+ completed: 2,
19
+ total: 4,
20
+ path: "/tmp/test-plan.md",
21
+ statusPill: "in progress",
22
+ steps: [
23
+ { checked: true, text: "First completed task" },
24
+ { checked: true, text: "Second completed task" },
25
+ { checked: false, text: "Third pending task" },
26
+ { checked: false, text: "Fourth pending task" },
27
+ ],
28
+ },
29
+ backgroundTasks: [],
30
+ timeSeries: {
31
+ windowMs: 300000,
32
+ buckets: 150,
33
+ bucketMs: 2000,
34
+ anchorMs: 1640995200000,
35
+ serverNowMs: 1640995500000,
36
+ series: [
37
+ {
38
+ id: "overall-main",
39
+ label: "Overall",
40
+ tone: "muted",
41
+ values: new Array(150).fill(0),
42
+ },
43
+ ],
44
+ },
45
+ }
46
+
47
+ // #when: converting to dashboard payload
48
+ const payload = toDashboardPayload(serverJson)
49
+
50
+ // #then: planProgress.steps should be preserved with correct structure
51
+ expect(payload.planProgress.steps).toBeDefined()
52
+ expect(payload.planProgress.steps).toEqual([
53
+ { checked: true, text: "First completed task" },
54
+ { checked: true, text: "Second completed task" },
55
+ { checked: false, text: "Third pending task" },
56
+ { checked: false, text: "Fourth pending task" },
57
+ ])
58
+ })
59
+
60
+ it('should handle missing or malformed planProgress.steps defensively', () => {
61
+ // #given: server JSON with malformed planProgress.steps
62
+ const serverJson = {
63
+ mainSession: {
64
+ agent: "sisyphus",
65
+ currentTool: "dashboard_start",
66
+ currentModel: "anthropic/claude-opus-4-5",
67
+ lastUpdatedLabel: "just now",
68
+ session: "test-session",
69
+ statusPill: "busy",
70
+ },
71
+ planProgress: {
72
+ name: "test-plan",
73
+ completed: 0,
74
+ total: 0,
75
+ path: "/tmp/test-plan.md",
76
+ statusPill: "not started",
77
+ steps: [
78
+ { checked: true, text: "Valid step" },
79
+ { checked: false }, // missing text
80
+ { text: "Missing checked" }, // missing checked
81
+ "invalid string", // wrong type
82
+ null, // null value
83
+ { checked: "not-boolean", text: "Invalid checked type" }, // wrong checked type
84
+ ],
85
+ },
86
+ backgroundTasks: [],
87
+ timeSeries: {
88
+ windowMs: 300000,
89
+ buckets: 150,
90
+ bucketMs: 2000,
91
+ anchorMs: 1640995200000,
92
+ serverNowMs: 1640995500000,
93
+ series: [
94
+ {
95
+ id: "overall-main",
96
+ label: "Overall",
97
+ tone: "muted",
98
+ values: new Array(150).fill(0),
99
+ },
100
+ ],
101
+ },
102
+ }
103
+
104
+ // #when: converting to dashboard payload
105
+ const payload = toDashboardPayload(serverJson)
106
+
107
+ // #then: should only include valid steps, ignore malformed ones
108
+ expect(payload.planProgress.steps).toEqual([
109
+ { checked: true, text: "Valid step" },
110
+ { checked: false, text: "Missing checked" }, // default checked to false
111
+ { checked: false, text: "Invalid checked type" }, // default checked to false for invalid boolean
112
+ ])
113
+ })
114
+
115
+ it('should handle non-array planProgress.steps', () => {
116
+ // #given: server JSON with non-array planProgress.steps
117
+ const serverJson = {
118
+ mainSession: {
119
+ agent: "sisyphus",
120
+ currentTool: "dashboard_start",
121
+ currentModel: "anthropic/claude-opus-4-5",
122
+ lastUpdatedLabel: "just now",
123
+ session: "test-session",
124
+ statusPill: "busy",
125
+ },
126
+ planProgress: {
127
+ name: "test-plan",
128
+ completed: 0,
129
+ total: 0,
130
+ path: "/tmp/test-plan.md",
131
+ statusPill: "not started",
132
+ steps: "not an array",
133
+ },
134
+ backgroundTasks: [],
135
+ timeSeries: {
136
+ windowMs: 300000,
137
+ buckets: 150,
138
+ bucketMs: 2000,
139
+ anchorMs: 1640995200000,
140
+ serverNowMs: 1640995500000,
141
+ series: [
142
+ {
143
+ id: "overall-main",
144
+ label: "Overall",
145
+ tone: "muted",
146
+ values: new Array(150).fill(0),
147
+ },
148
+ ],
149
+ },
150
+ }
151
+
152
+ // #when: converting to dashboard payload
153
+ const payload = toDashboardPayload(serverJson)
154
+
155
+ // #then: should handle non-array steps gracefully
156
+ expect(payload.planProgress.steps).toEqual([])
157
+ })
158
+ })
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { formatBackgroundTaskTimelineCell } from "./App"
3
+
4
+ describe('formatBackgroundTaskTimelineCell', () => {
5
+ it('should render "-" for queued regardless of timeline', () => {
6
+ // #given: queued status
7
+ const status = "queued"
8
+
9
+ // #when/#then
10
+ expect(formatBackgroundTaskTimelineCell(status, "")).toBe("-")
11
+ expect(formatBackgroundTaskTimelineCell(status, "2026-01-01T00:00:00Z: 2m")).toBe("-")
12
+ })
13
+
14
+ it('should render blank for unknown regardless of timeline', () => {
15
+ // #given: unknown status
16
+ const status = "unknown"
17
+
18
+ // #when/#then
19
+ expect(formatBackgroundTaskTimelineCell(status, "")).toBe("")
20
+ expect(formatBackgroundTaskTimelineCell(status, "2026-01-01T00:00:00Z: 2m")).toBe("")
21
+ })
22
+
23
+ it('should render timeline when present, otherwise "-" for other statuses', () => {
24
+ // #given: a non-queued, non-unknown status
25
+ const status = "running"
26
+
27
+ // #when/#then
28
+ expect(formatBackgroundTaskTimelineCell(status, "2026-01-01T00:00:00Z: 2m")).toBe("2026-01-01T00:00:00Z: 2m")
29
+ expect(formatBackgroundTaskTimelineCell(status, "")).toBe("-")
30
+ expect(formatBackgroundTaskTimelineCell(status, " ")).toBe("-")
31
+ })
32
+ })
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { computeToolCallsFetchPlan, toggleIdInSet } from "./App"
3
+
4
+ describe('computeToolCallsFetchPlan', () => {
5
+ it('should not fetch when sessionId is missing', () => {
6
+ // #given: no sessionId
7
+ const params = {
8
+ sessionId: null,
9
+ status: "running",
10
+ cachedState: "idle" as const,
11
+ cachedDataOk: false,
12
+ isExpanded: true
13
+ }
14
+
15
+ // #when
16
+ const result = computeToolCallsFetchPlan(params)
17
+
18
+ // #then
19
+ expect(result.shouldFetch).toBe(false)
20
+ expect(result.force).toBe(false)
21
+ })
22
+
23
+ it('should not fetch when not expanded', () => {
24
+ // #given: expanded is false
25
+ const params = {
26
+ sessionId: "session-123",
27
+ status: "done",
28
+ cachedState: "idle" as const,
29
+ cachedDataOk: false,
30
+ isExpanded: false
31
+ }
32
+
33
+ // #when
34
+ const result = computeToolCallsFetchPlan(params)
35
+
36
+ // #then
37
+ expect(result.shouldFetch).toBe(false)
38
+ expect(result.force).toBe(false)
39
+ })
40
+
41
+ it('should force fetch when status is "running" and expanded', () => {
42
+ // #given: running status
43
+ const params = {
44
+ sessionId: "session-123",
45
+ status: "running",
46
+ cachedState: "ok" as const,
47
+ cachedDataOk: true,
48
+ isExpanded: true
49
+ }
50
+
51
+ // #when
52
+ const result = computeToolCallsFetchPlan(params)
53
+
54
+ // #then
55
+ expect(result.shouldFetch).toBe(true)
56
+ expect(result.force).toBe(true)
57
+ })
58
+
59
+ it('should not fetch when non-running status and cached data is ok', () => {
60
+ // #given: done status with good cache
61
+ const params = {
62
+ sessionId: "session-123",
63
+ status: "done",
64
+ cachedState: "ok" as const,
65
+ cachedDataOk: true,
66
+ isExpanded: true
67
+ }
68
+
69
+ // #when
70
+ const result = computeToolCallsFetchPlan(params)
71
+
72
+ // #then
73
+ expect(result.shouldFetch).toBe(false)
74
+ expect(result.force).toBe(false)
75
+ })
76
+
77
+ it('should not fetch when already loading', () => {
78
+ // #given: loading state
79
+ const params = {
80
+ sessionId: "session-123",
81
+ status: "done",
82
+ cachedState: "loading" as const,
83
+ cachedDataOk: false,
84
+ isExpanded: true
85
+ }
86
+
87
+ // #when
88
+ const result = computeToolCallsFetchPlan(params)
89
+
90
+ // #then
91
+ expect(result.shouldFetch).toBe(false)
92
+ expect(result.force).toBe(false)
93
+ })
94
+
95
+ it('should fetch when non-running, not cached, not loading, and expanded', () => {
96
+ // #given: done status with no cache
97
+ const params = {
98
+ sessionId: "session-123",
99
+ status: "done",
100
+ cachedState: "idle" as const,
101
+ cachedDataOk: false,
102
+ isExpanded: true
103
+ }
104
+
105
+ // #when
106
+ const result = computeToolCallsFetchPlan(params)
107
+
108
+ // #then
109
+ expect(result.shouldFetch).toBe(true)
110
+ expect(result.force).toBe(false)
111
+ })
112
+
113
+ it('should handle case-insensitive status values', () => {
114
+ // #given: RUNNING in uppercase
115
+ const params = {
116
+ sessionId: "session-123",
117
+ status: "RUNNING",
118
+ cachedState: "idle" as const,
119
+ cachedDataOk: false,
120
+ isExpanded: true
121
+ }
122
+
123
+ // #when
124
+ const result = computeToolCallsFetchPlan(params)
125
+
126
+ // #then
127
+ expect(result.shouldFetch).toBe(true)
128
+ expect(result.force).toBe(true)
129
+ })
130
+
131
+ it('should handle whitespace in status values', () => {
132
+ // #given: running with whitespace
133
+ const params = {
134
+ sessionId: "session-123",
135
+ status: " running ",
136
+ cachedState: "idle" as const,
137
+ cachedDataOk: false,
138
+ isExpanded: true
139
+ }
140
+
141
+ // #when
142
+ const result = computeToolCallsFetchPlan(params)
143
+
144
+ // #then
145
+ expect(result.shouldFetch).toBe(true)
146
+ expect(result.force).toBe(true)
147
+ })
148
+ })
149
+
150
+ describe('toggleIdInSet', () => {
151
+ it('should add id when not present in set', () => {
152
+ // #given: empty set
153
+ const currentSet = new Set<string>()
154
+
155
+ // #when
156
+ const result = toggleIdInSet("task-1", currentSet)
157
+
158
+ // #then
159
+ expect(result.has("task-1")).toBe(true)
160
+ expect(result.size).toBe(1)
161
+ })
162
+
163
+ it('should remove id when already present in set', () => {
164
+ // #given: set with id already present
165
+ const currentSet = new Set(["task-1", "task-2"])
166
+
167
+ // #when
168
+ const result = toggleIdInSet("task-1", currentSet)
169
+
170
+ // #then
171
+ expect(result.has("task-1")).toBe(false)
172
+ expect(result.has("task-2")).toBe(true)
173
+ expect(result.size).toBe(1)
174
+ })
175
+
176
+ it('should not modify original set', () => {
177
+ // #given: original set
178
+ const originalSet = new Set(["task-1"])
179
+
180
+ // #when
181
+ const result = toggleIdInSet("task-2", originalSet)
182
+
183
+ // #then
184
+ expect(originalSet.has("task-1")).toBe(true)
185
+ expect(originalSet.has("task-2")).toBe(false)
186
+ expect(originalSet.size).toBe(1)
187
+ expect(result.has("task-1")).toBe(true)
188
+ expect(result.has("task-2")).toBe(true)
189
+ expect(result.size).toBe(2)
190
+ })
191
+ })
@@ -1,7 +1,7 @@
1
- import * as fs from "node:fs"
2
1
  import * as os from "node:os"
3
2
  import * as path from "node:path"
4
- import { describe, expect, it } from "vitest"
3
+ import * as fs from "node:fs"
4
+ import { describe, expect, it, vi } from "vitest"
5
5
  import { deriveBackgroundTasks } from "./background-tasks"
6
6
  import { getStorageRoots } from "./session"
7
7
 
@@ -602,6 +602,7 @@ describe("deriveBackgroundTasks", () => {
602
602
  expect(rows[0].toolCalls).toBe(0) // No tool calls in fallback session
603
603
  expect(rows[0].lastTool).toBe(null)
604
604
  expect(rows[0].status).toBe("unknown") // Should be unknown since session exists but no tool calls
605
+ expect(rows[0].timeline).toBe("") // Unknown status should emit no timeline
605
606
  })
606
607
 
607
608
  it("links sync delegate_task rows to Background sessions when forced-to-background but waited", () => {
@@ -704,4 +705,305 @@ describe("deriveBackgroundTasks", () => {
704
705
  expect((rows[0] as unknown as Record<string, unknown>).input).toBeUndefined()
705
706
  expect((rows[0] as unknown as Record<string, unknown>).state).toBeUndefined()
706
707
  })
708
+
709
+ it("derives lastModel from background session assistant metas", () => {
710
+ // #given
711
+ const storageRoot = mkStorageRoot()
712
+ const storage = getStorageRoots(storageRoot)
713
+ const mainSessionId = "ses_main"
714
+
715
+ const msgDir = path.join(storage.message, mainSessionId)
716
+ fs.mkdirSync(msgDir, { recursive: true })
717
+ const messageID = "msg_1"
718
+ fs.writeFileSync(
719
+ path.join(msgDir, `${messageID}.json`),
720
+ JSON.stringify({
721
+ id: messageID,
722
+ sessionID: mainSessionId,
723
+ role: "assistant",
724
+ time: { created: 1000 },
725
+ }),
726
+ "utf8"
727
+ )
728
+ const partDir = path.join(storage.part, messageID)
729
+ fs.mkdirSync(partDir, { recursive: true })
730
+ fs.writeFileSync(
731
+ path.join(partDir, "part_1.json"),
732
+ JSON.stringify({
733
+ id: "part_1",
734
+ sessionID: mainSessionId,
735
+ messageID,
736
+ type: "tool",
737
+ callID: "call_1",
738
+ tool: "delegate_task",
739
+ state: {
740
+ status: "completed",
741
+ input: {
742
+ run_in_background: true,
743
+ description: "Model scan",
744
+ subagent_type: "explore",
745
+ },
746
+ },
747
+ }),
748
+ "utf8"
749
+ )
750
+
751
+ const projectID = "proj"
752
+ const sessDir = path.join(storage.session, projectID)
753
+ fs.mkdirSync(sessDir, { recursive: true })
754
+ fs.writeFileSync(
755
+ path.join(sessDir, "ses_child.json"),
756
+ JSON.stringify({
757
+ id: "ses_child",
758
+ projectID,
759
+ directory: "/tmp/project",
760
+ title: "Background: Model scan",
761
+ parentID: mainSessionId,
762
+ time: { created: 1500, updated: 1500 },
763
+ }),
764
+ "utf8"
765
+ )
766
+
767
+ const childMsgDir = path.join(storage.message, "ses_child")
768
+ fs.mkdirSync(childMsgDir, { recursive: true })
769
+ const childMsgId = "msg_child"
770
+ fs.writeFileSync(
771
+ path.join(childMsgDir, `${childMsgId}.json`),
772
+ JSON.stringify({
773
+ id: childMsgId,
774
+ sessionID: "ses_child",
775
+ role: "assistant",
776
+ time: { created: 2000 },
777
+ providerID: "openai",
778
+ modelID: "gpt-5.2",
779
+ }),
780
+ "utf8"
781
+ )
782
+
783
+ // #when
784
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
785
+
786
+ // #then
787
+ expect(rows.length).toBe(1)
788
+ expect(rows[0].lastModel).toBe("openai/gpt-5.2")
789
+ })
790
+
791
+ it("orders lastModel by time.created over file mtime", () => {
792
+ // #given
793
+ const storageRoot = mkStorageRoot()
794
+ const storage = getStorageRoots(storageRoot)
795
+ const mainSessionId = "ses_main"
796
+
797
+ const msgDir = path.join(storage.message, mainSessionId)
798
+ fs.mkdirSync(msgDir, { recursive: true })
799
+ const messageID = "msg_1"
800
+ fs.writeFileSync(
801
+ path.join(msgDir, `${messageID}.json`),
802
+ JSON.stringify({
803
+ id: messageID,
804
+ sessionID: mainSessionId,
805
+ role: "assistant",
806
+ time: { created: 1000 },
807
+ }),
808
+ "utf8"
809
+ )
810
+ const partDir = path.join(storage.part, messageID)
811
+ fs.mkdirSync(partDir, { recursive: true })
812
+ fs.writeFileSync(
813
+ path.join(partDir, "part_1.json"),
814
+ JSON.stringify({
815
+ id: "part_1",
816
+ sessionID: mainSessionId,
817
+ messageID,
818
+ type: "tool",
819
+ callID: "call_1",
820
+ tool: "delegate_task",
821
+ state: {
822
+ status: "completed",
823
+ input: {
824
+ run_in_background: true,
825
+ description: "Ordering scan",
826
+ subagent_type: "explore",
827
+ },
828
+ },
829
+ }),
830
+ "utf8"
831
+ )
832
+
833
+ const projectID = "proj"
834
+ const sessDir = path.join(storage.session, projectID)
835
+ fs.mkdirSync(sessDir, { recursive: true })
836
+ fs.writeFileSync(
837
+ path.join(sessDir, "ses_child.json"),
838
+ JSON.stringify({
839
+ id: "ses_child",
840
+ projectID,
841
+ directory: "/tmp/project",
842
+ title: "Background: Ordering scan",
843
+ parentID: mainSessionId,
844
+ time: { created: 1500, updated: 1500 },
845
+ }),
846
+ "utf8"
847
+ )
848
+
849
+ const childMsgDir = path.join(storage.message, "ses_child")
850
+ fs.mkdirSync(childMsgDir, { recursive: true })
851
+ const olderMsgId = "msg_older"
852
+ const newerMsgId = "msg_newer"
853
+
854
+ fs.writeFileSync(
855
+ path.join(childMsgDir, `${newerMsgId}.json`),
856
+ JSON.stringify({
857
+ id: newerMsgId,
858
+ sessionID: "ses_child",
859
+ role: "assistant",
860
+ time: { created: 3000 },
861
+ providerID: "openai",
862
+ modelID: "gpt-newer",
863
+ }),
864
+ "utf8"
865
+ )
866
+ fs.writeFileSync(
867
+ path.join(childMsgDir, `${olderMsgId}.json`),
868
+ JSON.stringify({
869
+ id: olderMsgId,
870
+ sessionID: "ses_child",
871
+ role: "assistant",
872
+ time: { created: 1000 },
873
+ providerID: "openai",
874
+ modelID: "gpt-older",
875
+ }),
876
+ "utf8"
877
+ )
878
+
879
+ // #when
880
+ const rows = deriveBackgroundTasks({ storage, mainSessionId })
881
+
882
+ // #then
883
+ expect(rows.length).toBe(1)
884
+ expect(rows[0].lastModel).toBe("openai/gpt-newer")
885
+ })
886
+
887
+ it("memoizes background session reads for shared sessionId", () => {
888
+ // #given
889
+ const storageRoot = mkStorageRoot()
890
+ const storage = getStorageRoots(storageRoot)
891
+ const mainSessionId = "ses_main"
892
+
893
+ // Clear any previous mock state
894
+ vi.clearAllMocks()
895
+
896
+ const msgDir = path.join(storage.message, mainSessionId)
897
+ fs.mkdirSync(msgDir, { recursive: true })
898
+ const messageID = "msg_1"
899
+ fs.writeFileSync(
900
+ path.join(msgDir, `${messageID}.json`),
901
+ JSON.stringify({
902
+ id: messageID,
903
+ sessionID: mainSessionId,
904
+ role: "assistant",
905
+ time: { created: 1000 },
906
+ }),
907
+ "utf8"
908
+ )
909
+ const partDir = path.join(storage.part, messageID)
910
+ fs.mkdirSync(partDir, { recursive: true })
911
+ fs.writeFileSync(
912
+ path.join(partDir, "part_1.json"),
913
+ JSON.stringify({
914
+ id: "part_1",
915
+ sessionID: mainSessionId,
916
+ messageID,
917
+ type: "tool",
918
+ callID: "call_1",
919
+ tool: "delegate_task",
920
+ state: {
921
+ status: "completed",
922
+ input: {
923
+ run_in_background: true,
924
+ description: "Memo scan",
925
+ subagent_type: "explore",
926
+ },
927
+ },
928
+ }),
929
+ "utf8"
930
+ )
931
+ fs.writeFileSync(
932
+ path.join(partDir, "part_2.json"),
933
+ JSON.stringify({
934
+ id: "part_2",
935
+ sessionID: mainSessionId,
936
+ messageID,
937
+ type: "tool",
938
+ callID: "call_2",
939
+ tool: "delegate_task",
940
+ state: {
941
+ status: "completed",
942
+ input: {
943
+ run_in_background: true,
944
+ description: "Memo scan",
945
+ subagent_type: "explore",
946
+ },
947
+ },
948
+ }),
949
+ "utf8"
950
+ )
951
+
952
+ const projectID = "proj"
953
+ const sessDir = path.join(storage.session, projectID)
954
+ fs.mkdirSync(sessDir, { recursive: true })
955
+ fs.writeFileSync(
956
+ path.join(sessDir, "ses_child.json"),
957
+ JSON.stringify({
958
+ id: "ses_child",
959
+ projectID,
960
+ directory: "/tmp/project",
961
+ title: "Background: Memo scan",
962
+ parentID: mainSessionId,
963
+ time: { created: 1500, updated: 1500 },
964
+ }),
965
+ "utf8"
966
+ )
967
+
968
+ const childMsgDir = path.join(storage.message, "ses_child")
969
+ fs.mkdirSync(childMsgDir, { recursive: true })
970
+ const childMsgId = "msg_child"
971
+ fs.writeFileSync(
972
+ path.join(childMsgDir, `${childMsgId}.json`),
973
+ JSON.stringify({
974
+ id: childMsgId,
975
+ sessionID: "ses_child",
976
+ role: "assistant",
977
+ time: { created: 2000 },
978
+ providerID: "openai",
979
+ modelID: "gpt-5.2",
980
+ }),
981
+ "utf8"
982
+ )
983
+
984
+ // Create typed wrapper that records calls and delegates to fs.readdirSync
985
+ const readdirCalls: Array<[fs.PathLike]> = []
986
+ const readdirSync = ((path: fs.PathLike, options?: any): any => {
987
+ if (typeof options === 'object' && options.withFileTypes) {
988
+ return fs.readdirSync(path, options)
989
+ }
990
+ readdirCalls.push([path])
991
+ return fs.readdirSync(path, 'utf8')
992
+ }) as typeof fs.readdirSync
993
+
994
+ const fsLike = {
995
+ readFileSync: fs.readFileSync,
996
+ readdirSync,
997
+ existsSync: fs.existsSync,
998
+ statSync: fs.statSync,
999
+ }
1000
+
1001
+ // #when
1002
+ const rows = deriveBackgroundTasks({ storage, mainSessionId, fs: fsLike })
1003
+
1004
+ // #then
1005
+ const backgroundReads = readdirCalls.filter((call) => call[0] === childMsgDir)
1006
+ expect(rows.length).toBe(2)
1007
+ expect(backgroundReads.length).toBe(1)
1008
+ })
707
1009
  })