apify-test-tools 0.5.7-beta.2 → 0.5.7-beta.4

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/bin/diff-changes.ts +127 -0
  3. package/bin/diff-json-schema.ts +43 -0
  4. package/bin/git.ts +13 -4
  5. package/bin/main.ts +9 -5
  6. package/bin/utils.ts +2 -119
  7. package/dist/bin/diff-changes.d.ts +16 -0
  8. package/dist/bin/diff-changes.d.ts.map +1 -0
  9. package/dist/bin/diff-changes.js +91 -0
  10. package/dist/bin/diff-changes.js.map +1 -0
  11. package/dist/bin/diff-json-schema.d.ts +7 -0
  12. package/dist/bin/diff-json-schema.d.ts.map +1 -0
  13. package/dist/bin/diff-json-schema.js +40 -0
  14. package/dist/bin/diff-json-schema.js.map +1 -0
  15. package/dist/bin/git.d.ts +1 -1
  16. package/dist/bin/git.d.ts.map +1 -1
  17. package/dist/bin/git.js +12 -4
  18. package/dist/bin/git.js.map +1 -1
  19. package/dist/bin/main.js +8 -5
  20. package/dist/bin/main.js.map +1 -1
  21. package/dist/bin/utils.d.ts +0 -14
  22. package/dist/bin/utils.d.ts.map +1 -1
  23. package/dist/bin/utils.js +1 -88
  24. package/dist/bin/utils.js.map +1 -1
  25. package/dist/test/unit/bin/diff-changes.test.d.ts +2 -0
  26. package/dist/test/unit/bin/diff-changes.test.d.ts.map +1 -0
  27. package/dist/test/unit/bin/diff-changes.test.js +152 -0
  28. package/dist/test/unit/bin/diff-changes.test.js.map +1 -0
  29. package/dist/test/unit/bin/diff.test.d.ts +2 -0
  30. package/dist/test/unit/bin/diff.test.d.ts.map +1 -0
  31. package/dist/test/unit/bin/diff.test.js +133 -0
  32. package/dist/test/unit/bin/diff.test.js.map +1 -0
  33. package/dist/test/unit/parse-commit.test.js +1 -1
  34. package/dist/test/unit/parse-commit.test.js.map +1 -1
  35. package/dist/test/unit/should-built-and-test.test.js +93 -30
  36. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +1 -1
  39. package/test/unit/bin/diff-changes.test.ts +175 -0
  40. package/test/unit/bin/diff.test.ts +214 -0
  41. package/test/unit/parse-commit.test.ts +2 -1
  42. package/test/unit/should-built-and-test.test.ts +115 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.5.7-beta.2",
3
+ "version": "0.5.7-beta.4",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -0,0 +1,175 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { getChangedActors, maybeParseActorFolder } from '../../../bin/diff-changes.js';
4
+ import * as DiffJsonSchema from '../../../bin/diff-json-schema.js';
5
+ import type { ActorConfig } from '../../../bin/types.js';
6
+
7
+ const miniActor: ActorConfig = { actorName: 'foo/bar', folder: 'actors/foo_bar', isStandalone: false };
8
+ const standaloneActor: ActorConfig = { actorName: 'standalone', folder: 'standalone-actors/standalone', isStandalone: true };
9
+ const actorConfigs = [miniActor, standaloneActor];
10
+
11
+ const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
12
+
13
+ describe('maybeParseActorFolder', () => {
14
+ it('returns actorName for actors/ path', () => {
15
+ expect(maybeParseActorFolder('actors/foo_bar/actor.json')).toEqual({ isActorFolder: true, actorName: 'foo/bar' });
16
+ });
17
+
18
+ it('returns actorName for standalone-actors/ path', () => {
19
+ expect(maybeParseActorFolder('standalone-actors/my_actor/main.ts')).toEqual({ isActorFolder: true, actorName: 'my/actor' });
20
+ });
21
+
22
+ it('returns false for top-level file', () => {
23
+ expect(maybeParseActorFolder('package.json')).toEqual({ isActorFolder: false });
24
+ });
25
+
26
+ it('returns false for path with no file inside actor folder', () => {
27
+ expect(maybeParseActorFolder('actors/foo_bar')).toEqual({ isActorFolder: false });
28
+ });
29
+
30
+ it('returns false for unrelated folder', () => {
31
+ expect(maybeParseActorFolder('src/utils.ts')).toEqual({ isActorFolder: false });
32
+ });
33
+ });
34
+
35
+ describe('getChangedActors', () => {
36
+ beforeEach(() => {
37
+ vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(false);
38
+ });
39
+
40
+ it('returns empty array when no files changed', () => {
41
+ expect(getChangedActors({ filepathsChanged: [], actorConfigs, commits })).toEqual([]);
42
+ });
43
+
44
+ it('returns empty array when only ignored top-level files changed', () => {
45
+ const result = getChangedActors({
46
+ filepathsChanged: ['.gitignore', 'README.md', '.husky/pre-commit', '.vscode/settings.json'],
47
+ actorConfigs,
48
+ commits,
49
+ });
50
+ expect(result).toEqual([]);
51
+ });
52
+
53
+ it('returns the actor when a functional file in its folder changes', () => {
54
+ const result = getChangedActors({
55
+ filepathsChanged: ['actors/foo_bar/src/main.ts'],
56
+ actorConfigs,
57
+ commits,
58
+ });
59
+ expect(result).toEqual([miniActor]);
60
+ });
61
+
62
+ it('returns actor when isLatest and README in actor folder changed (cosmetic)', () => {
63
+ const result = getChangedActors({
64
+ filepathsChanged: ['actors/foo_bar/README.md'],
65
+ actorConfigs,
66
+ commits,
67
+ isLatest: true,
68
+ });
69
+ expect(result).toEqual([miniActor]);
70
+ });
71
+
72
+ it('does not return actor when not isLatest and only README changed (cosmetic)', () => {
73
+ const result = getChangedActors({
74
+ filepathsChanged: ['actors/foo_bar/README.md'],
75
+ actorConfigs,
76
+ commits,
77
+ isLatest: false,
78
+ });
79
+ expect(result).toEqual([]);
80
+ });
81
+
82
+ it('returns actor when isLatest and JSON file has only cosmetic changes', () => {
83
+ vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(true);
84
+ const result = getChangedActors({
85
+ filepathsChanged: ['actors/foo_bar/actor.json'],
86
+ actorConfigs,
87
+ commits,
88
+ isLatest: true,
89
+ });
90
+ expect(result).toEqual([miniActor]);
91
+ });
92
+
93
+ it('does not return actor when not isLatest and JSON file has only cosmetic changes', () => {
94
+ vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(true);
95
+ const result = getChangedActors({
96
+ filepathsChanged: ['actors/foo_bar/actor.json'],
97
+ actorConfigs,
98
+ commits,
99
+ isLatest: false,
100
+ });
101
+ expect(result).toEqual([]);
102
+ });
103
+
104
+ it('returns actor when JSON file has functional changes', () => {
105
+ vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(false);
106
+ const result = getChangedActors({
107
+ filepathsChanged: ['actors/foo_bar/actor.json'],
108
+ actorConfigs,
109
+ commits,
110
+ });
111
+ expect(result).toEqual([miniActor]);
112
+ });
113
+
114
+ it('returns all non-standalone actors when a non-actor-folder functional file changes', () => {
115
+ const result = getChangedActors({
116
+ filepathsChanged: ['shared/utils.ts'],
117
+ actorConfigs,
118
+ commits,
119
+ });
120
+ expect(result).toContainEqual(miniActor);
121
+ expect(result).not.toContainEqual(standaloneActor);
122
+ });
123
+
124
+ it('does not include standalone actor in all-actors expansion from changelog', () => {
125
+ const result = getChangedActors({
126
+ filepathsChanged: ['CHANGELOG.md'],
127
+ actorConfigs,
128
+ commits,
129
+ isLatest: true,
130
+ });
131
+ expect(result).toContainEqual(miniActor);
132
+ expect(result).not.toContainEqual(standaloneActor);
133
+ });
134
+
135
+ it('includes standalone actor when its own folder changes', () => {
136
+ const result = getChangedActors({
137
+ filepathsChanged: ['standalone-actors/standalone/src/main.ts'],
138
+ actorConfigs,
139
+ commits,
140
+ });
141
+ expect(result).toContainEqual(standaloneActor);
142
+ });
143
+
144
+ it('deduplicates actors when multiple files in same actor folder change', () => {
145
+ const result = getChangedActors({
146
+ filepathsChanged: ['actors/foo_bar/src/main.ts', 'actors/foo_bar/package.json'],
147
+ actorConfigs,
148
+ commits,
149
+ });
150
+ expect(result).toHaveLength(1);
151
+ expect(result).toContainEqual(miniActor);
152
+ });
153
+
154
+ it('handles mixed changes: returns both mini and standalone actors', () => {
155
+ const result = getChangedActors({
156
+ filepathsChanged: [
157
+ 'actors/foo_bar/src/main.ts',
158
+ 'standalone-actors/standalone/Dockerfile',
159
+ ],
160
+ actorConfigs,
161
+ commits,
162
+ });
163
+ expect(result).toContainEqual(miniActor);
164
+ expect(result).toContainEqual(standaloneActor);
165
+ });
166
+
167
+ it('file paths are matched case-insensitively', () => {
168
+ const result = getChangedActors({
169
+ filepathsChanged: ['Actors/FOO_BAR/Main.ts'],
170
+ actorConfigs,
171
+ commits,
172
+ });
173
+ expect(result).toEqual([miniActor]);
174
+ });
175
+ });
@@ -0,0 +1,214 @@
1
+ import type { MockInstance } from 'vitest';
2
+ import { beforeEach, describe, expect, it, test, vi } from 'vitest';
3
+
4
+ import { isCosmeticOnlyJsonSchemaChange } from '../../../bin/diff-json-schema.js';
5
+ import * as Utils from '../../../bin/utils.js';
6
+
7
+ const commits = [
8
+ { sha: 'Commit1', author: '', date: '', message: '' },
9
+ { sha: 'Commit3', author: '', date: '', message: '' },
10
+ ];
11
+
12
+ describe('isCosmeticOnlyJsonSchemaChange', () => {
13
+ let gitCommandSpy: MockInstance;
14
+
15
+ beforeEach(() => {
16
+ gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
17
+ });
18
+
19
+ const mockGitCalls = (oldJson: string, newJson: string) => {
20
+ gitCommandSpy
21
+ .mockReturnValueOnce(oldJson)
22
+ .mockReturnValueOnce(newJson);
23
+ };
24
+
25
+ test('returns true when nothing changed', () => {
26
+ const json = JSON.stringify({ type: 'string', title: 'My field' });
27
+ mockGitCalls(json, json);
28
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
29
+ });
30
+
31
+ test('returns true when only title changes', () => {
32
+ mockGitCalls(
33
+ JSON.stringify({ type: 'string', title: 'Old title' }),
34
+ JSON.stringify({ type: 'string', title: 'New title' }),
35
+ );
36
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
37
+ });
38
+
39
+ test('returns true when only description changes', () => {
40
+ mockGitCalls(
41
+ JSON.stringify({ type: 'string', description: 'Old desc' }),
42
+ JSON.stringify({ type: 'string', description: 'New desc' }),
43
+ );
44
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
45
+ });
46
+
47
+ test('returns true when only example changes', () => {
48
+ mockGitCalls(
49
+ JSON.stringify({ type: 'string', example: 'foo' }),
50
+ JSON.stringify({ type: 'string', example: 'bar' }),
51
+ );
52
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
53
+ });
54
+
55
+ test('returns true when only enumTitles changes', () => {
56
+ mockGitCalls(
57
+ JSON.stringify({ type: 'string', enum: ['a', 'b'], enumTitles: ['Option A', 'Option B'] }),
58
+ JSON.stringify({ type: 'string', enum: ['a', 'b'], enumTitles: ['Choice A', 'Choice B'] }),
59
+ );
60
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
61
+ });
62
+
63
+ test('returns true when only sectionCaption changes', () => {
64
+ mockGitCalls(
65
+ JSON.stringify({ sectionCaption: 'Old caption' }),
66
+ JSON.stringify({ sectionCaption: 'New caption' }),
67
+ );
68
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
69
+ });
70
+
71
+ test('returns true when only sectionDescription changes', () => {
72
+ mockGitCalls(
73
+ JSON.stringify({ sectionDescription: 'Old' }),
74
+ JSON.stringify({ sectionDescription: 'New' }),
75
+ );
76
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
77
+ });
78
+
79
+ test('returns false when a functional field (type) changes', () => {
80
+ mockGitCalls(
81
+ JSON.stringify({ type: 'string', title: 'My field' }),
82
+ JSON.stringify({ type: 'integer', title: 'My field' }),
83
+ );
84
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
85
+ });
86
+
87
+ test('returns false when a functional field is added', () => {
88
+ mockGitCalls(
89
+ JSON.stringify({ title: 'My field' }),
90
+ JSON.stringify({ title: 'My field', type: 'string' }),
91
+ );
92
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
93
+ });
94
+
95
+ test('returns false when a functional field is removed', () => {
96
+ mockGitCalls(
97
+ JSON.stringify({ type: 'string', title: 'My field' }),
98
+ JSON.stringify({ title: 'My field' }),
99
+ );
100
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
101
+ });
102
+
103
+ test('returns true when non-functional fields change at nested level (input schema properties)', () => {
104
+ mockGitCalls(
105
+ JSON.stringify({
106
+ type: 'object',
107
+ properties: {
108
+ myField: { type: 'string', title: 'Old title', description: 'Old desc', example: 'old' },
109
+ },
110
+ }),
111
+ JSON.stringify({
112
+ type: 'object',
113
+ properties: {
114
+ myField: { type: 'string', title: 'New title', description: 'New desc', example: 'new' },
115
+ },
116
+ }),
117
+ );
118
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
119
+ });
120
+
121
+ test('returns false when a functional nested field changes alongside non-functional ones', () => {
122
+ mockGitCalls(
123
+ JSON.stringify({
124
+ type: 'object',
125
+ properties: { myField: { type: 'string', title: 'Old title' } },
126
+ }),
127
+ JSON.stringify({
128
+ type: 'object',
129
+ properties: { myField: { type: 'integer', title: 'New title' } },
130
+ }),
131
+ );
132
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
133
+ });
134
+
135
+ test('returns false when a new property is added to input schema', () => {
136
+ mockGitCalls(
137
+ JSON.stringify({ properties: { fieldA: { type: 'string' } } }),
138
+ JSON.stringify({ properties: { fieldA: { type: 'string' }, fieldB: { type: 'integer' } } }),
139
+ );
140
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
141
+ });
142
+
143
+ test('returns false when a property is removed from input schema', () => {
144
+ mockGitCalls(
145
+ JSON.stringify({ properties: { fieldA: { type: 'string' }, fieldB: { type: 'integer' } } }),
146
+ JSON.stringify({ properties: { fieldA: { type: 'string' } } }),
147
+ );
148
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
149
+ });
150
+
151
+ test('returns false when invalid JSON in old content', () => {
152
+ mockGitCalls('not json', '{}');
153
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
154
+ });
155
+
156
+ test('returns false when invalid JSON in new content', () => {
157
+ mockGitCalls('{}', 'not json');
158
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
159
+ });
160
+
161
+ test('returns false when array content changes (not under a non-functional key)', () => {
162
+ mockGitCalls(
163
+ JSON.stringify({ enum: [1, 2, 3] }),
164
+ JSON.stringify({ enum: [1, 2, 4] }),
165
+ );
166
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
167
+ });
168
+
169
+ test('returns true when example array changes (non-functional key)', () => {
170
+ mockGitCalls(
171
+ JSON.stringify({ type: 'array', example: [1, 2, 3] }),
172
+ JSON.stringify({ type: 'array', example: [4, 5, 6] }),
173
+ );
174
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
175
+ });
176
+
177
+ test('returns true for a realistic actor.json with only title/description change', () => {
178
+ mockGitCalls(
179
+ JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'Old Title', description: 'Old description', version: '1.0' }),
180
+ JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'New Title', description: 'New description', version: '1.0' }),
181
+ );
182
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
183
+ });
184
+
185
+ test('returns true when fields under properties are reordered', () => {
186
+ mockGitCalls(
187
+ JSON.stringify({
188
+ type: 'object',
189
+ properties: { fieldA: { type: 'string' }, fieldB: { type: 'integer' }, fieldC: { type: 'boolean' } },
190
+ }),
191
+ JSON.stringify({
192
+ type: 'object',
193
+ properties: { fieldC: { type: 'boolean' }, fieldA: { type: 'string' }, fieldB: { type: 'integer' } },
194
+ }),
195
+ );
196
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
197
+ });
198
+
199
+ test('returns false for a realistic actor.json with version change', () => {
200
+ mockGitCalls(
201
+ JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'My Actor', version: '1.0' }),
202
+ JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'My Actor', version: '1.1' }),
203
+ );
204
+ expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
205
+ });
206
+
207
+ it('uses correct git refs: first-commit parent for old, last commit for new', () => {
208
+ const json = JSON.stringify({});
209
+ mockGitCalls(json, json);
210
+ isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json');
211
+ expect(gitCommandSpy).toHaveBeenCalledWith('git show Commit1~:actors/foo/actor.json');
212
+ expect(gitCommandSpy).toHaveBeenCalledWith('git show Commit3:actors/foo/actor.json');
213
+ });
214
+ });
@@ -1,4 +1,5 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
2
+
2
3
  import { GIT_FORMAT_SEPARATOR, parseCommit } from '../../bin/git.js';
3
4
 
4
5
  describe('parseCommit', () => {
@@ -1,7 +1,9 @@
1
- import { describe, expect, test } from 'vitest';
1
+ import type { MockInstance } from 'vitest';
2
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
3
 
3
- import type { ActorConfig } from '../../bin/types.js';
4
- import { getChangedActors } from '../../bin/utils.js';
4
+ import { getChangedActors } from '../../bin/diff-changes.js';
5
+ import * as DiffJsonSchema from '../../bin/diff-json-schema.js';
6
+ import type { ActorConfig, Commit } from '../../bin/types.js';
5
7
 
6
8
  describe('Should build and test parser', () => {
7
9
  // From https://github.com/apify-store/testing-repo-for-github-actions
@@ -23,85 +25,168 @@ describe('Should build and test parser', () => {
23
25
  },
24
26
  ];
25
27
 
26
- test('Ignores dev-only readme', async () => {
28
+ const commits: Commit[] = [
29
+ { sha: 'Commit1', author: '', date: '', message: '' },
30
+ { sha: 'Commit3', author: '', date: '', message: '' },
31
+ ];
32
+
33
+ let isCosmeticOnlyJsonSchemaSpy: MockInstance;
34
+
35
+ beforeEach(() => {
36
+ // Default: JSON changes are functional (triggers build/test)
37
+ isCosmeticOnlyJsonSchemaSpy = vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(false);
38
+ });
39
+
40
+ test('Ignores dev-only readme', () => {
27
41
  const FILES = ['README.md', 'code/README.md', 'shared/README.md'];
28
42
 
29
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
43
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
30
44
 
31
45
  expect(actorsChanged).toEqual([]);
32
- expect(codeChanged).toBe(false);
33
46
  });
34
47
 
35
- test('Ignores other ignored files and folders', async () => {
48
+ test('Ignores other ignored files and folders', () => {
36
49
  const FILES = ['.vscode/', '.gitignore', '.husky/', '.eslintrc', '.editorconfig', '.actor/'];
37
50
 
38
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
51
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
39
52
 
40
53
  expect(actorsChanged).toEqual([]);
41
- expect(codeChanged).toBe(false);
42
54
  });
43
55
 
44
- test('Only builds latest for all Actors', async () => {
56
+ test('Only builds latest for all Actors', () => {
45
57
  const FILES = ['shared/CHANGELOG.md', 'CHANGELOG.md'];
46
58
 
47
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES });
59
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES, commits });
48
60
 
49
61
  expect(actorsChanged).toEqual(ACTOR_CONFIGS.filter(({ isStandalone }) => !isStandalone));
50
- expect(codeChanged).toBe(false);
51
62
  });
52
63
 
53
- test('Code updated, tests miniactors', async () => {
64
+ test('Code updated, tests miniactors', () => {
54
65
  const FILES = ['code/src/main.ts', 'package.json'];
55
66
 
56
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
67
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
57
68
 
58
69
  expect(actorsChanged).toEqual(ACTOR_CONFIGS.filter(({ isStandalone }) => !isStandalone));
59
- expect(codeChanged).toBe(true);
60
70
  });
61
71
 
62
- test('Specific Actor functionality configs updated', async () => {
72
+ test('Specific Actor functionality configs updated', () => {
63
73
  const FILES = ['actors/lukaskrivka_testing-github-integration-1/.actor/actor.json', 'standalone-actors/lukaskrivka_test-standalone/Dockerfile'];
74
+ // Default mock returns false = functional change
64
75
 
65
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
76
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
66
77
 
67
78
  expect(actorsChanged).toEqual([ACTOR_CONFIGS[0], ACTOR_CONFIGS[2]]);
68
- expect(codeChanged).toBe(false);
69
79
  });
70
80
 
71
- test('src/main.ts updated', async () => {
81
+ test('src/main.ts updated', () => {
72
82
  const FILES = [
73
83
  'src/main.ts',
74
84
  ];
75
85
 
76
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES });
86
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES, commits });
77
87
 
78
88
  expect(actorsChanged).toEqual(ACTOR_CONFIGS.slice(0, 2));
79
- expect(codeChanged).toBe(false);
80
89
  });
81
90
 
82
- test('Miniactor, Code and standalone actor updated,', async () => {
91
+ test('Miniactor, Code and standalone actor updated,', () => {
83
92
  const FILES = [
84
93
  'actors/lukaskrivka_testing-github-integration-1/.actor/actor.json',
85
94
  'code/src/main.ts',
86
95
  'standalone-actors/lukaskrivka_test-standalone/Dockerfile',
87
96
  ];
97
+ // Default mock returns false = functional change for the JSON file
88
98
 
89
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
99
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
90
100
 
91
101
  expect(actorsChanged).toEqual(ACTOR_CONFIGS);
92
- expect(codeChanged).toBe(true);
93
102
  });
94
103
 
95
- test('Specific Actor non-functional configs updated', async () => {
104
+ test('Specific Actor non-functional configs updated', () => {
96
105
  const FILES = ['actors/lukaskrivka_testing-github-integration-2/.actor/README.md', 'standalone-actors/lukaskrivka_test-standalone/CHANGELOG.md'];
97
106
 
98
- const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
107
+ const actorsChanged = getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES, commits });
108
+
109
+ expect(actorsChanged).toEqual([]);
110
+ });
111
+
112
+ test('JSON file with cosmetic-only changes in PR context (isLatest=false) skips tests', () => {
113
+ const FILES = ['actors/lukaskrivka_testing-github-integration-1/.actor/actor.json'];
114
+ isCosmeticOnlyJsonSchemaSpy.mockReturnValue(true);
115
+
116
+ const actorsChanged = getChangedActors({
117
+ actorConfigs: ACTOR_CONFIGS,
118
+ isLatest: false,
119
+ filepathsChanged: FILES,
120
+ commits,
121
+ });
122
+
123
+ expect(actorsChanged).toEqual([]);
124
+ });
125
+
126
+ test('JSON file with cosmetic-only changes in latest context still triggers build', () => {
127
+ const FILES = ['actors/lukaskrivka_testing-github-integration-1/.actor/actor.json'];
128
+ isCosmeticOnlyJsonSchemaSpy.mockReturnValue(true);
129
+
130
+ const actorsChanged = getChangedActors({
131
+ actorConfigs: ACTOR_CONFIGS,
132
+ isLatest: true,
133
+ filepathsChanged: FILES,
134
+ commits,
135
+ });
136
+
137
+ expect(actorsChanged).toEqual([ACTOR_CONFIGS[0]]);
138
+ });
139
+
140
+ test('JSON file with functional changes triggers tests', () => {
141
+ const FILES = ['actors/lukaskrivka_testing-github-integration-1/.actor/actor.json'];
142
+ // Default mock returns false = functional change
143
+
144
+ const actorsChanged = getChangedActors({
145
+ actorConfigs: ACTOR_CONFIGS,
146
+ isLatest: false,
147
+ filepathsChanged: FILES,
148
+ commits,
149
+ });
150
+
151
+ expect(actorsChanged).toEqual([ACTOR_CONFIGS[0]]);
152
+ });
153
+
154
+ test('Mix: one actor has cosmetic-only JSON change, another has functional JSON change', () => {
155
+ const FILES = [
156
+ 'actors/lukaskrivka_testing-github-integration-1/.actor/actor.json',
157
+ 'actors/lukaskrivka_testing-github-integration-2/.actor/input_schema.json',
158
+ ];
159
+ // Actor 1 JSON is cosmetic-only, actor 2 JSON is functional
160
+ isCosmeticOnlyJsonSchemaSpy.mockImplementation((_commits, filepath: string) =>
161
+ !filepath.includes('input_schema.json'),
162
+ );
163
+
164
+ const actorsChanged = getChangedActors({
165
+ actorConfigs: ACTOR_CONFIGS,
166
+ isLatest: false,
167
+ filepathsChanged: FILES,
168
+ commits,
169
+ });
170
+
171
+ // Only the second actor (functional change) should be built and tested
172
+ expect(actorsChanged).toEqual([ACTOR_CONFIGS[1]]);
173
+ });
174
+
175
+ test('Standalone actor with cosmetic-only JSON change in PR context skips tests', () => {
176
+ const FILES = ['standalone-actors/lukaskrivka_test-standalone/.actor/actor.json'];
177
+ isCosmeticOnlyJsonSchemaSpy.mockReturnValue(true);
178
+
179
+ const actorsChanged = getChangedActors({
180
+ actorConfigs: ACTOR_CONFIGS,
181
+ isLatest: false,
182
+ filepathsChanged: FILES,
183
+ commits,
184
+ });
99
185
 
100
186
  expect(actorsChanged).toEqual([]);
101
- expect(codeChanged).toBe(false);
102
187
  });
103
188
 
104
- test('Google Maps real user-case that had undefined', async () => {
189
+ test('Google Maps real user-case that had undefined', () => {
105
190
  const FILES = ['actors/compass_Google-Maps-Reviews-Scraper/.actor/INPUT_SCHEMA.json', 'actors/compass_crawler-google-places/.actor/INPUT_SCHEMA.json',
106
191
  'code/src/consts.ts', 'code/src/crawlers/cheerio/routes.ts', 'code/src/detail_page_handle.ts', 'code/src/enqueue_places.ts',
107
192
  'code/src/helper-classes/initialize-all.ts', 'code/src/helper-classes/stats.ts', 'code/src/helper-classes/unmatched-categories.ts',
@@ -156,11 +241,10 @@ describe('Should build and test parser', () => {
156
241
  },
157
242
  ];
158
243
 
159
- const { actorsChanged, codeChanged } = await getChangedActors({
160
- actorConfigs: ACTOR_CONFIGS_GOOGLE_MAPS, isLatest: false, filepathsChanged: FILES,
244
+ const actorsChanged = getChangedActors({
245
+ actorConfigs: ACTOR_CONFIGS_GOOGLE_MAPS, isLatest: false, filepathsChanged: FILES, commits,
161
246
  });
162
247
 
163
248
  expect(actorsChanged).toEqual(ACTOR_CONFIGS_GOOGLE_MAPS.filter(({ isStandalone }) => !isStandalone));
164
- expect(codeChanged).toBe(true);
165
249
  });
166
250
  });