cli icon indicating copy to clipboard operation
cli copied to clipboard

Custom matcher for ui logger

Open BioPhoton opened this issue 1 year ago • 2 comments

User story

As a developer I want to have a good signal to noise ration and want to write my tests with as less boilerplate as possible. Therefor I need a custom matcher for the UI logger.

Bad example:

// ui().logger.error('Something went wrong');

expect(ui().logger.getRenderer().getLogs()).toStrictEqual([
  {
    stream: 'stderr',
    message: '[ red(error) ] Something went wrong'
  }
])

Suggested matcher:

  • expect(ui()).toHaveLogged('...')
  • expect(ui()).toHaveLoggedNth(1, '...')
  • expect(ui()).not.toHaveLogged())

Conversation: https://github.com/code-pushup/cli/pull/487#discussion_r1517516666

Good example:

// ui().logger.error('Something went wrong');

expect(ui()).toHaveLoggedLevelNth(1, 'error');
expect(ui()).toHaveLoggedMessageNth(1, 'Something went wrong');

Acceptance criteria

I want to be able to use the custom matcher in the following libs:

  • [ ] vitest
  • jest (optional)

Internal logic:

  • [ ] it handles removes ASCII color expect(msg.replace(/\u001B\[\d+m/g, '')).toBe("[ blue(info) ] This is is blue in terminal")
  • custom matcher toMatchPlainCharacter instead of removeColorCodes (optional) I want to be able to have different matcher present:
  • [ ] toHaveLoggedLevel('...')
  • [ ] toHaveLoggedLevelNth(1, '...')
  • [ ] toHaveLoggedMessage('...')
  • [ ] toHaveLoggedMessageNth(1, '...')

Implementation details

import { expect } from 'vitest';
import {cliui} from "@poppinss/cliui";

declare module 'vitest' {
  export interface Assert {
    toHaveLoggedMessage(expected: string): void;
    toHaveLoggedMessageNth(nth: number, expected: string): void;
  }
}

export type CliUi = ReturnType<typeof cliui>;
// Implementation of toHaveLogged
expect.extend({
  toHaveLoggedMessage(received: CliUi, expected: string) {
    const logs = received.logger.getRenderer().getLogs();
    const pass = logs.some(log => log.message === expected);
    return {
      pass,
      message: () => pass
        ? `expected not to have logged: ${expected}`
        : `expected to have logged: ${expected}`,
    };
  },

  toHaveLoggedMessageNth(received: CliUi, nth: number, expected: string) {
    const logs = received.logger.getRenderer().getLogs();
    const pass = (logs[nth - 1]?.message === expected) ?? false;
    return {
      pass,
      message: () => pass
        ? `expected not to have logged at position ${nth}: ${expected}`
        : `expected to have logged at position ${nth}: ${expected}`,
    };
  },
});

Usage in global-setup.ts

import './testing/test-utils/src/lib/matcher/ui-logger';

BioPhoton avatar Mar 08 '24 22:03 BioPhoton

Suggestions:

  • Add a helper function to test-utils that returns Nth log directly as a string.
  • If applicable, distinguish formatted (bold, colour code) and unformatted logs.

Tlacenka avatar Mar 12 '24 12:03 Tlacenka

Feedback from @matejchalk:

On balance, I'd go with no style testing. The text content is the key part of the assertion for me. Although if create a custom matcher, we could theoretically support both. E.g. .toHaveLogged('...') would match unstyled text, but .toHaveLoggedStyled('...') would be available for matching styles also. Alternatively, .toHaveLogged('...') could try to match both unstyled and styled text under the hood and only fail if neither matches (bit less transparent, but perhaps more convenient?). 🤔

BioPhoton avatar Mar 20 '24 10:03 BioPhoton