Ways to configure Jasmine tests to disregard tabs within a multiline string

I have a method that produces a multi-line string as shown below:

line 1 of example
line 2 of example
line 3 of example

and I want to be able to write my code like this:

it('should return the correct output', () => {
  expect(service.getData('bar').toEqual(
  `line 1 of example
    line 2 of example
    line 3 of example`);
});

but unfortunately, it results in an error due to extra whitespace before some lines being added by the test:

Expected 'line 1 of example
line 2 of example
line 3 of example' to equal 'line 1 of example
    line 2 of example
    line 3 of example'.

I am aware that I can manually adjust my tests by including newline characters or removing the whitespace, but it is not visually appealing.

It would be great if there was a tool available for stripping out unnecessary indentations like this in a safe and reliable manner. Is there such a utility, do I need to create my own solution, or should I stick with using newline characters?

Answer №1

It seems like there isn't a ready-made solution available, so you may need to create your own custom approach.

A few options come to mind quickly, with the custom matcher being the most elegant in my opinion.

  1. Using toContain to verify expected content is present in the output string
  2. Using toMatch - to check using regular expressions (RegEx)
  3. Implementing custom matchers - which are highly reusable and easy to understand

For more information, refer to , , and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions

Function under test:

function getData(x){
    return `
    example line 1
    example line 2
    example line 3
    `
}

Test setup:

function trimmedLines(untrimmed){
    // Trim leading and trailing spaces and newlines, then split at inner newlines
    const lines = untrimmed.trim().replace(/^\n+/, '').replace(/\n+$/, '').split('\n')
    const trimmedLines = []
    lines.forEach(line => {
        // Trim and add each line to a new array
        trimmedLines.push(line.trim())
    });
    // Return a trimmed string
    return trimmedLines.join('\n')
}

const customMatchers = {
    ToEqualTrimmed: function(matchersUtil) {
        return {
            compare: function(actual, expected) {
                if (expected === undefined) expected = '';
                const result = { };
                result.pass = matchersUtil.equals(
                    trimmedLines(actual),
                    trimmedLines(expected)
                )

                if (result.pass) {
                    result.message = "Expected " + actual + " to be trimmed equal to" + expected;
                } else {
                    result.message = "Expected " + actual + " to be trimmed equal to" + expected + ' But was not!';
                }
                return result;
            }
        };
    }
};

beforeEach(function() {
    jasmine.addMatchers(customMatchers);
});

describe("Outer test description", () => {
    it('should retrieve the correct data', () => {
        const output = getData('foo')
        // This assertion fails
        expect(output).toEqual(
          `example line 1
            example line 2
            example line 3`);
        // These three pass
        expect(output).toContain('example line 1')
        expect(output).toContain('example line 2')
        expect(output).toContain('example line 3')
        // This passes as well
        expect(output).toMatch('[ \n]*example line 1[ \n]*example line 2[ \n]*example line 3[ \n]*');
        // Also passes
        expect(output).ToEqualTrimmed(
          `example line 1
            example line 2
            example line 3`);

    });

});

The [ \n]* matches zero or more spaces or newlines.

Answer №2

Recalling my involvement in a past project, TSLint, I remembered the existence of a dedent method for assessing strings of TypeScript code as input/output within their linting process. Upon discovering the source code for the dedent method, I came across an example usage with JSON code it operates on.

Naturally, I decided to tweak and simplify this method to suit my needs better, resulting in the following implementation that appears functional:

export function dedent(inputStrings: TemplateStringsArray, ...values: any[]) {
  //Converting template string array into a unified string
  const fullString = inputStrings.reduce(
    (accumulator, str, i) => `${accumulator}${values[i - 1]}${str}`
  );

  //Remove leading whitespace from each line
  return fullString
    .split('\n')
    .map((line) => line.replace(/^[\t\s]*/, ''))
    .join('\n');
}

This modified approach now allows me to utilize the method without the need for enclosing parentheses, offering a more streamlined workflow:

import { dedent } from 'test-utils';

it('should obtain correct data', () => {
  expect(service.getData('foo'))
    .toEqual(dedent`example line 1
                    example line 2
                    example line 3`);
});

At this initial phase of my current project, this tailored solution seems to align well with my testing requirements. Should I continue moving forward with this approach, or is there potentially a pitfall I might overlook down the road?

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

typescript import { node } from types

Exploring the possibilities with an electron application developed in typescript. The main focus is on finding the appropriate approach for importing an external module. Here is my typescript configuration: { "compilerOptions": { "target": "es6", ...

The Tools of the Trade: TypeScript Tooling

Trying out the amazing Breeze Typescript Entity Generator tool but encountering an error consistently. Error: Experiencing difficulty in locating the default implementation of the 'modelLibrary' interface. Options include 'ko', 'b ...

Using TypeScript to declare ambient types with imported declarations

In my TypeScript project, I have a declaration file set up like this: // myapp.d.ts declare namespace MyApp { interface MyThing { prop1: string prop2: number } } It works perfectly and I can access this namespace throughout my project without ...

Unable to implement new ecmascript decorators within typescript version 2.4.2

Check out this example code: function enumerable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.enumerable = value; }; } class A { @enumerable(false) a: number = 1 b: number ...

Establishing MQTT Connection in Ionic 3

I am facing a challenge with implementing Publish-Subscribe methods in my Ionic 3 application. After consulting the information on this page, I attempted to link MQTT with my Ionic 3 application. Can anyone guide me on how to successfully connect MQTT wi ...

Having trouble compiling Typescript code when attempting to apply material-ui withStyles function

I have the following dependencies: "@material-ui/core": "3.5.1", "react": "16.4.0", "typescript": "2.6.1" Currently, I am attempting to recreate the material-ui demo for SimpleListMenu. However, I am encountering one final compile error that is proving ...

Unsure about Typescript object structures {} and []?

I am new to using lists and object lists in Typescript and I'm unsure of how they function. In the code snippet below, a few objects are created and some temporary values are assigned to them through a loop. However, my goal is to have the console log ...

What is the best method for accessing a specific key value in a JSON file?

I am looking to extract a specific field data from a JSON list Here is the example JSON data: { "data": [ {"answer": "Yes","email": "aaaa.cpm","color": "Maroon"}, {"answer": "No","email": "bbbb.cpm","color": "White"}, {"answer": "Yes","email" ...

Is the indigo-pink color scheme fully implemented after installing @angular/material and scss using ng add command?

After running ng add @angular/material, we are prompted to choose a CSS framework and theme. I opted for indigo-pink and scss. Will the material components automatically inherit this theme, or do we need to take additional steps? When using normal CSS (wi ...

There is no registered handler for channel - Electron IPC handle/invoke

When using my Electron app, I keep encountering the error message "No handler registered for 'channel-name' at EventEmitter../lib/renderer/api/ipc-renderer.ts.ipcRenderer.invoke (electron/js2c/renderer_init.js:1163:19)". This issue seems to stem ...

The types definition for OpenSeadragon is lacking a property

I have developed an application using React Typescript and have integrated OpenSeadragon () for displaying images. To incorporate type definitions for OpenSeadragon, I am utilizing @types/openseadragon: https://www.npmjs.com/package/@types/openseadragon M ...

Discovering the versatility of Typescript objects

I want to define a type that follows this rule: If the property container is present, then expect the property a. If the property item is present, then expect the property b. Both container and item cannot exist at the same time. The code I would expect ...

What is the best way to create a TypeScript interface or type definition for my constant variable?

I'm facing challenges in defining an interface or type for my dataset, and encountering some errors. Here is the incorrect interfaces and code that I'm using: interface IVehicle { [key: number]: { model: string, year: number }; } interface IV ...

Struggle to deduce the generic parameter of a superior interface in Typescript

Struggling with the lack of proper type inference, are there any solutions to address this issue? interface I<T> {}; class C implements I<string> {}; function test<T, B extends I<T>>(b: B): T { return null as any; // simply for ...

classes_1.Individual is not a callable

I am facing some difficulties with imports and exports in my self-made TypeScript project. Within the "classes" folder, I have individual files for each class that export them. To simplify usage in code, I created an "index.ts" file that imports all class ...

When using Express, the node.js application is able to skip over conditional statements and proceed

In my express app, I have 2 if statements serving as middleware. The first one runs smoothly, but the second one seems to skip and executes the next() function without checking the second if statement. app.use((req: Request, res: Response, next: express. ...

Expanding the capabilities of button properties in MUI

My goal is to customize the MUI5 components by extending their ButtonProps interface and utilizing the component prop. Following the guidelines provided in the documentation, I implemented the solution as shown in the code snippet below: import Button, ...

Adding dynamic values to nested form groups in Angular Form Array

After following a tutorial on creating a reactive form in an Angular application, I managed to implement it successfully. However, I encountered an issue when trying to add an additional control called "setNumber" to the form array. I want this control to ...

What is the process for ensuring that an object's property values must include specific substrings?

Exploring possibilities for an API configuration system and seeking guidance on typing a config object. Clarification: This pertains to a resources/endpoints configuration for the API, where each resource/endpoint has a defined path containing specific pa ...

To ensure compatibility with Angular, the data path "/fileReplacements/1/replace" should adhere to the pattern ".(([cm]?j|t)sx?|json)$"

After upgrading Angular from version 10 to 14, I encountered an issue in the build process related to replacing the favicon.ico using fileReplacements like so: "fileReplacements": [ { "replace": "src/favicon.ico", ...