Sonarlint false positive for test file with fast-check tests

If your question is about SonarLint in the IntelliJ Platform, VS Code, Visual Studio, or Eclipse, please post it in that sub-category.

Otherwise, please provide:

  • Operating system: linux mint
  • IDE name and flavor/env: webstorm

And a thorough description of the problem / question:

I’m using sonarlint on a typescript project.
I have a test file for some utility functions that contain only property tests (using the GitHub - dubzzz/fast-check: Property based testing framework for JavaScript (like QuickCheck) written in TypeScript library, more specifically the @fast-check/vitest package):

describe('toISODate', () => {
  test.prop([fc.date()])('should match expected format', (date) =>
    /^[+-]?\d+-\d{2}-\d{2}$/.test(toDateISOString(date)),
  );

  test.prop([fc.date()])('should return a parseable date', (date) => !Number.isNaN(Date.parse(toDateISOString(date))));

  test.prop([fc.date()])('should be parsed as input date without time', (date) => {
    const withoutTime = new Date(date);
    withoutTime.setUTCHours(0);
    withoutTime.setUTCMinutes(0);
    withoutTime.setUTCSeconds(0);
    withoutTime.setUTCMilliseconds(0);
    const parsed = new Date(Date.parse(toDateISOString(date)));

    return parsed.getTime() === withoutTime.getTime();
  });
});
// and other similar describe blocks…

Sonarlint incorrectly raises the “Test files should contain at least one test case” error.

Hello Alain,

Thank you for bringing up this issue. We have opened a ticket for it which you can follow here: [JS-187] - Jira

Hello @Alain_Dubrulle ,

Sorry for the late reply. We are currently working on your issue and we have not been able to reproduce it. We suspect that something important is missing from the code sample that you shared, something that actually triggers the false positive.

Would you mind sharing a minimal reproducible example that triggers the FP?

Thanks a lot in advance,

Eric.

Hi Eric. Here is the full test file. We are using vite/vitest.

import { fc, test } from '@fast-check/vitest';
import { formatRelativeToNow, toDateISOString, toDateTimeISOString, toTimeISOString } from 'utils/dates/format-date';
import { addDays, addHours, addMilliseconds, addMonths, addYears } from 'date-fns';
import { TESTS_REFERENCE_DATE } from 'tests/constants';

describe('toDateTimeISOString', () => {
  test.prop([fc.date()])('should match expected format', (date) =>
    /^[+-]?\d+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(toDateTimeISOString(date)),
  );

  test.prop([fc.date()])(
    'should return a parseable date',
    (date) => !Number.isNaN(Date.parse(toDateTimeISOString(date))),
  );

  test.prop([fc.date()])(
    'should be parsed as input date',
    (date) => Date.parse(toDateTimeISOString(date)) === date.getTime(),
  );
});

describe('toDateISOString', () => {
  test.prop([fc.date()])('should match expected format', (date) =>
    /^[+-]?\d+-\d{2}-\d{2}$/.test(toDateISOString(date)),
  );

  test.prop([fc.date()])('should return a parseable date', (date) => !Number.isNaN(Date.parse(toDateISOString(date))));

  test.prop([fc.date()])('should be parsed as input date without time', (date) => {
    const withoutTime = new Date(date);
    withoutTime.setUTCHours(0);
    withoutTime.setUTCMinutes(0);
    withoutTime.setUTCSeconds(0);
    withoutTime.setUTCMilliseconds(0);
    const parsed = new Date(Date.parse(toDateISOString(date)));

    return parsed.getTime() === withoutTime.getTime();
  });
});

describe('toTimeISOString', () => {
  test.prop([fc.date()])('should match expected format', (date) =>
    /^\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(toTimeISOString(date)),
  );
});

describe('i18nextFromNowFormatter', () => {
  const relativeTimeFormat = new Intl.RelativeTimeFormat('en', { numeric: 'always' });
  const now = TESTS_REFERENCE_DATE;

  test.prop([fc.integer().filter((it) => Math.abs(it) > 0 && Math.abs(it) < 9999)])(
    'should format in years if date diff is over a year',
    (diff) => {
      vi.setSystemTime(now);
      expect(formatRelativeToNow(addYears(now, diff), relativeTimeFormat)).toContain('year');
    },
  );

  test.prop([fc.integer().filter((it) => Math.abs(it) > 0 && Math.abs(it) < 12)])(
    'should format in months if date diff is over a month',
    (diff) => {
      vi.setSystemTime(now);
      expect(formatRelativeToNow(addMonths(now, diff), relativeTimeFormat)).toContain('month');
    },
  );

  test.prop([fc.integer().filter((it) => Math.abs(it) > 0 && Math.abs(it) < 12)])(
    'should format in days if date diff is over a day',
    (diff) => {
      vi.setSystemTime(now);
      expect(formatRelativeToNow(addDays(now, diff), relativeTimeFormat)).toContain('day');
    },
  );

  test.prop([fc.integer().filter((it) => Math.abs(it) > 0 && Math.abs(it) < 24)])(
    'should format in hours if date diff is over an hour',
    (diff) => {
      vi.setSystemTime(now);
      expect(formatRelativeToNow(addHours(now, diff), relativeTimeFormat)).toContain('hour');
    },
  );

  test.prop([fc.integer().filter((it) => Math.abs(it) > 0 && Math.abs(it) < 60 * 60 * 1_000)])(
    'should format in minutes if diff is under an hour',
    (diff) => {
      vi.setSystemTime(now);
      expect(formatRelativeToNow(addMilliseconds(now, diff), relativeTimeFormat)).toContain('minute');
    },
  );
});

1 Like

Thank you. I’m able to trigger S2187 with your code sample. Can you confirm that this is also the issue that is raised on your side?

Yes, it is the issue raised by the sonarlint plugin.

Thank you Alain. I’ve got everything needed to fix the issue. I’ve updated [JS-187] - Jira accordingly.

Sorry for the delay, once again, and thanks for your patience.

Eric.