Skip to content

Commit d0abf24

Browse files
style: sonar fixes
1 parent 835f447 commit d0abf24

3 files changed

Lines changed: 32 additions & 71 deletions

File tree

src/sync-crowdin-issues.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ async function syncExistingIssue(ghIssue, crowdinIssue, projectId, projectSlug)
483483
if (ghIssue.body !== expectedBody) patch.body = expectedBody;
484484

485485
// Compare label sets (order-independent).
486-
const currentLabels = (ghIssue.labels ?? []).map((l) => (typeof l === 'string' ? l : l.name)).sort();
487-
const desiredLabels = [...expectedLabels].sort();
486+
const currentLabels = (ghIssue.labels ?? []).map((l) => (typeof l === 'string' ? l : l.name)).sort((a, b) => a.localeCompare(b));
487+
const desiredLabels = [...expectedLabels].sort((a, b) => a.localeCompare(b));
488488
if (JSON.stringify(currentLabels) !== JSON.stringify(desiredLabels)) {
489489
patch.labels = expectedLabels;
490490
// Ensure any new labels exist before applying them.

tests/sync-crowdin-distribution.test.js

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ describe('constants', () => {
3737

3838
describe('collectBody', () => {
3939
it('resolves with the concatenated buffer from all data events', async () => {
40-
const { EventEmitter } = require('node:events');
4140
const mockRes = new EventEmitter();
4241

4342
const promise = collectBody(mockRes);
@@ -51,7 +50,6 @@ describe('collectBody', () => {
5150
});
5251

5352
it('rejects when the stream emits an error', async () => {
54-
const { EventEmitter } = require('node:events');
5553
const mockRes = new EventEmitter();
5654

5755
const promise = collectBody(mockRes);
@@ -132,11 +130,36 @@ describe('saveFile', () => {
132130
});
133131
});
134132

133+
// Shared test helpers
134+
135+
const { EventEmitter } = require('node:events');
136+
137+
/**
138+
* Helper: creates a minimal IncomingMessage-like EventEmitter.
139+
*/
140+
function makeResponse({ statusCode = 200, headers = {}, body = '' } = {}) {
141+
const res = new EventEmitter();
142+
res.statusCode = statusCode;
143+
res.headers = headers;
144+
// Schedule body/end asynchronously so the promise chain runs first
145+
setImmediate(() => {
146+
res.emit('data', typeof body === 'string' ? Buffer.from(body) : body);
147+
res.emit('end');
148+
});
149+
return res;
150+
}
151+
152+
/**
153+
* Helper: creates a minimal https.get return stub with an .on() method.
154+
*/
155+
function makeReq() {
156+
return { on: jest.fn().mockReturnThis() };
157+
}
158+
135159
// fetchUrl
136160

137161
describe('fetchUrl', () => {
138162
const https = require('node:https');
139-
const { EventEmitter } = require('node:events');
140163

141164
let httpsGetSpy;
142165

@@ -148,28 +171,6 @@ describe('fetchUrl', () => {
148171
jest.restoreAllMocks();
149172
});
150173

151-
/**
152-
* Helper: creates a minimal IncomingMessage-like EventEmitter.
153-
*/
154-
function makeResponse({ statusCode = 200, headers = {}, body = 'ok' }) {
155-
const res = new EventEmitter();
156-
res.statusCode = statusCode;
157-
res.headers = headers;
158-
// Schedule body/end asynchronously so the promise chain runs first
159-
setImmediate(() => {
160-
res.emit('data', Buffer.from(body));
161-
res.emit('end');
162-
});
163-
return res;
164-
}
165-
166-
/**
167-
* Helper: creates a minimal https.get return stub with an .on() method.
168-
*/
169-
function makeReq() {
170-
return { on: jest.fn().mockReturnThis() };
171-
}
172-
173174
it('resolves with the body buffer for a 200 response', async () => {
174175
const res = makeResponse({ body: 'hello world' });
175176
httpsGetSpy.mockImplementation((_url, cb) => {
@@ -280,7 +281,6 @@ describe('syncDistribution', () => {
280281
const fs = require('node:fs');
281282
const path = require('node:path');
282283
const os = require('node:os');
283-
const { EventEmitter } = require('node:events');
284284

285285
let tmpDir;
286286
let httpsGetSpy;
@@ -295,21 +295,6 @@ describe('syncDistribution', () => {
295295
fs.rmSync(tmpDir, { recursive: true, force: true });
296296
});
297297

298-
function makeResponse({ statusCode = 200, headers = {}, body = '' }) {
299-
const res = new EventEmitter();
300-
res.statusCode = statusCode;
301-
res.headers = headers;
302-
setImmediate(() => {
303-
res.emit('data', typeof body === 'string' ? Buffer.from(body) : body);
304-
res.emit('end');
305-
});
306-
return res;
307-
}
308-
309-
function makeReq() {
310-
return { on: jest.fn().mockReturnThis() };
311-
}
312-
313298
it('returns true when all files are fetched successfully', async () => {
314299
const manifest = {
315300
timestamp: 12345,
@@ -449,7 +434,6 @@ describe('main', () => {
449434
const fs = require('node:fs');
450435
const path = require('node:path');
451436
const os = require('node:os');
452-
const { EventEmitter } = require('node:events');
453437

454438
let tmpDir;
455439
let httpsGetSpy;
@@ -480,20 +464,6 @@ describe('main', () => {
480464
}
481465
});
482466

483-
function makeResponse({ statusCode = 200, headers = {}, body = '' }) {
484-
const res = new EventEmitter();
485-
res.statusCode = statusCode;
486-
res.headers = headers;
487-
setImmediate(() => {
488-
res.emit('data', Buffer.from(body));
489-
res.emit('end');
490-
});
491-
return res;
492-
}
493-
494-
function makeReq() {
495-
return { on: jest.fn().mockReturnThis() };
496-
}
497467

498468
it('completes successfully when all distributions sync without error', async () => {
499469
// main() reads DISTRIBUTIONS at module load time, so we call syncDistribution

tests/sync-crowdin-issues.test.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -367,49 +367,44 @@ describe('ensureIssueLabels', () => {
367367
jest.clearAllMocks();
368368
jest.spyOn(console, 'log').mockImplementation(() => {});
369369
jest.spyOn(console, 'warn').mockImplementation(() => {});
370+
mockCreateLabel.mockResolvedValue({});
370371
});
371372

372373
afterEach(() => jest.restoreAllMocks());
373374

374375
it('creates the type label and one language label for a simple language', async () => {
375-
mockCreateLabel.mockResolvedValue({});
376376
await ensureIssueLabels({ issueType: 'source_mistake', languageId: 'fr' });
377377
const names = mockCreateLabel.mock.calls.map((c) => c[0].name);
378378
expect(names).toContain('type:source-mistake');
379379
expect(names).toContain('lang:fr');
380380
});
381381

382382
it('creates two language labels for a compound language (pt-BR)', async () => {
383-
mockCreateLabel.mockResolvedValue({});
384383
await ensureIssueLabels({ issueType: 'general_question', languageId: 'pt-BR' });
385384
const names = mockCreateLabel.mock.calls.map((c) => c[0].name);
386385
expect(names).toContain('lang:pt');
387386
expect(names).toContain('lang:pt-BR');
388387
});
389388

390389
it('uses TYPE_LABEL_COLOR for the type label', async () => {
391-
mockCreateLabel.mockResolvedValue({});
392390
await ensureIssueLabels({ issueType: 'source_mistake', languageId: 'fr' });
393391
const typeCall = mockCreateLabel.mock.calls.find((c) => c[0].name === 'type:source-mistake');
394392
expect(typeCall[0].color).toBe(TYPE_LABEL_COLOR);
395393
});
396394

397395
it('uses LANG_LABEL_COLOR for language labels', async () => {
398-
mockCreateLabel.mockResolvedValue({});
399396
await ensureIssueLabels({ issueType: 'source_mistake', languageId: 'fr' });
400397
const langCall = mockCreateLabel.mock.calls.find((c) => c[0].name === 'lang:fr');
401398
expect(langCall[0].color).toBe(LANG_LABEL_COLOR);
402399
});
403400

404401
it('skips language label creation when languageId is absent', async () => {
405-
mockCreateLabel.mockResolvedValue({});
406402
await ensureIssueLabels({ issueType: 'general_question', languageId: null });
407403
const names = mockCreateLabel.mock.calls.map((c) => c[0].name);
408404
expect(names.every((n) => !n.startsWith('lang:'))).toBe(true);
409405
});
410406

411407
it('uses the raw issueType as description when not in TYPE_MAP', async () => {
412-
mockCreateLabel.mockResolvedValue({});
413408
await ensureIssueLabels({ issueType: 'unknown_type', languageId: null });
414409
const typeCall = mockCreateLabel.mock.calls.find((c) => c[0].name === 'type:unknown-type');
415410
expect(typeCall[0].description).toBe('unknown_type');
@@ -610,7 +605,7 @@ describe('buildIssueBody', () => {
610605
it('places the marker as the last line', () => {
611606
const body = buildIssueBody(baseIssue, projectId);
612607
const lines = body.split('\n');
613-
expect(MARKER_RE.test(lines[lines.length - 1])).toBe(true);
608+
expect(MARKER_RE.test(lines.at(-1))).toBe(true);
614609
});
615610

616611
it('includes the Crowdin ID in the table', () => {
@@ -622,6 +617,8 @@ describe('buildIssueBody', () => {
622617
expect(body).toContain('### Language Managers');
623618
expect(body).toContain('@fake-github');
624619
expect(body).toContain('https://crowdin.com/profile/fake-crowdin');
620+
// Marker must still be the last line even when the managers section is present
621+
expect(MARKER_RE.test(body.split('\n').at(-1))).toBe(true);
625622
});
626623

627624
it('includes multiple GitHub mentions for a language with multiple managers', () => {
@@ -647,12 +644,6 @@ describe('buildIssueBody', () => {
647644
const body = buildIssueBody({ ...baseIssue, languageId: undefined }, projectId);
648645
expect(body).not.toContain('### Language Managers');
649646
});
650-
651-
it('places the marker as the last line even when managers section is present', () => {
652-
const body = buildIssueBody({ ...baseIssue, languageId: 'xx' }, projectId);
653-
const lines = body.split('\n');
654-
expect(MARKER_RE.test(lines[lines.length - 1])).toBe(true);
655-
});
656647
});
657648

658649
// fetchProjectSlug

0 commit comments

Comments
 (0)