Error: $initialize.t is not a valid method (Vite, Vue 3, TypeScript, vue-i18n)

I've been working on unit testing a simple component that utilizes translations with the vue-i18n module. Here's an overview of the files involved:

src/i18n/index.ts

import { createI18n } from 'vue-i18n';
    
export function loadLanguages() {
  const context = import.meta.globEager('./languages/*.ts');

  const languages: Record<string, any> = {};

  const langs = Object.keys(context);
  for (const key of langs) {
    if (key === './index.ts') return;
    const { lang } = context[key];
    const name = key.replace(/(\.\/languages\/|\.ts)/g, '');
    languages[name] = lang;
  }

  return languages;
}

export const i18n = createI18n({
  legacy: false,
  locale: 'es',
  fallbackLocale: 'es',
  messages: loadLanguages(),
  missingWarn: false,
  fallbackWarn: false,
});

export const i18nGlobal = i18n.global;

export function setLanguage(locale: string) {
  i18n.global.locale.value = locale;
}

src/i18n/hooks/helper.ts

import { useI18n } from 'vue-i18n';
import { watch } from 'vue';
import { useGetters } from '@store-common/hooks/helpers';

export const useI18nGlobal = () => useI18n({ useScope: 'global' });

export const useI18nLocal = () => {
  const { locale } = useI18nGlobal();

  const local = useI18n({
    locale: locale.value as string,
    inheritLocale: true,
    useScope: 'local',
  });

  const { getLocale } = useGetters();

  watch(getLocale, (loc: string) => {
    local.locale.value = loc;
  });

  return local;
};

src/components/Example.vue

<template>
  <div>
    {{ greeting }}
    {{ t('common.btn.send') }}
    {{ translate }}
  </div>
</template>

<script setup lang="ts">
import { useI18nLocal } from '@i18n/hooks/helper';

const { t } = useI18nLocal();

const greeting = 'Vue and TDD';
const translate = t('common.btn.send');
</script>

src/components/tests/Example.spec.ts

import { shallowMount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import Example from '../Example.vue';

describe('Example.vue', () => {
  it('Traducciones i18n', () => {
    const wrapper = shallowMount(Example);
    expect(wrapper.text()).toMatch('Vue and TDD');
    expect(wrapper.vm.translate).toMatch('Enviar');
  });
});

package.json

{
  "name": "PROJECT_NAME",
  "version": "1.0.0",
  "scripts": {
    ...
    "test:unit": "vitest --environment jsdom --dir src/ --coverage",
    ...
  },
}

While running the yarn test:unit command specified in the package.json, I encountered the following error message:

cmd> yarn test:unit

yarn run v1.22.11
warning package.json: No license field
warning ..\..\package.json: No license field
$ vitest --environment jsdom --dir src/ --coverage Example

 DEV  v0.25.5 C:/Users/jgomezle/projects/HISVAR_FRONT
      Coverage enabled with c8

 ❯ src/shared/components/__tests__/Example.spec.ts (1)
   ❯ Example.vue (1)
     × Traducciones i18n

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/shared/components/__tests__/Example.spec.ts > Example.vue > Traducciones i18n
TypeError: $setup.t is not a function
 ❯ Proxy._sfc_render src/shared/components/Example.vue:20:207

...

As we can see from the error <code>TypeError: $setup.t is not a function, it appears that the t function from the i18n module cannot be found to carry out the translations.

I have attempted to mock the t function in various ways during shallowMount, but none have proven effective as the error persists. These are the different configurations I have tried:

const wrapper = shallowMount(Example, {
      mocks: {
        t: (str) => str,
        setup: {
          t: (str) => str,
        },
        $setup: {
          t: (str) => str,
        },
      },
      global: {
        mocks: {
          useI18n: {
            t: (msg) => msg,
            $t: (msg) => msg,
          },
          $setup: {
            t: (msg) => msg,
            $t: (msg) => msg,
          },
          setup: {
            t: (msg) => msg,
            $t: (msg) => msg,
          },
          t: (msg) => msg,
          $t: (msg) => msg,
        },
        // plugins: [i18n],
      },
    });

I have also experimented with these configurations, but unfortunately, the issue persists:

import { config } from '@vue/test-utils';

config.global.mocks = {
  $t: (msg) => msg,
  t: (msg) => msg,
  $setup: {
    $t: (msg) => msg,
    t: (msg) => msg,
  },
  setup: {
    $t: (msg) => msg,
    t: (msg) => msg,
  },
};

Answer №1

The reason behind this error is that you are directly calling the t() function in your template. To avoid this error, it is recommended to call the t() method within a computed property and then use this computed property in your template. Here's an example:

// Define a computed property in your <script setup>
const buttonText = computed(() => t('common.btn.send'));

// Use the computed property in your <template>
{{ buttonText }}

If you discover the exact reason for this error, please share it with me! In the meantime, I will continue using this approach.

Answer №2

Ha, stumbled upon your question while searching for a solution :)

Fortunately, this method did the trick for me. I recalled seeing someone else facing the same issue and here we are again :)

The main idea that eluded both of us is that when you mount a component in tests, it operates independently from your application. It functions as its own separate entity. Therefore, if you want to incorporate translations (and not just simulate them), you must manually initialize the i18n plugin for each test execution (or globally for all tests, as explained in the answer provided in my referenced link).

Answer №3

My answer on Stackoverflow got posted as a comment due to its simplicity. Let's give it another shot...

I recently discovered a workaround for mocking vue-i18n and detailed the process here.

To sum it up, here is how I implemented it in my test:

import { useI18n } from "vue-i18n";
// ... other imports

vi.mock("vue-i18n");

useI18n.mockReturnValue({
  t: (tKey) => tKey,
});

config.global.mocks = {
  $t: (tKey) => tKey,
};

// tests

In the future, I plan to explore implementing this globally rather than in each individual test file.

Answer №4

Example setup files to the rescue:

// custom-config.ts
export default defineConfig({
  root: './src/',
  test: {
    setupFiles: ['./custom-setup.ts'],
  }
})
// src/modules/language.ts
import { createLanguageModule } from 'vue-language';

export default createLanguageModule({
  // ...
});
// src/custom-setup.ts
import language from '@/modules/language';
import { config } from '@vue/test-utils';

// @ts-expect-error type
if (!globalThis.loaded) {
  config.global.plugins = [language];
  // @ts-expect-error type
  globalThis.loaded = true;
}

Answer №5

If you're working with Cypress instead of directly using Vitest and encountering the same issue, I came across a helpful solution on this GitHub page.

The approach involves adding a listener to the mount function within Cypress to incorporate elements into the global context (specifically i18n in this case).

To achieve this, simply insert the following code snippet into the cypress/support/component.ts file:

import { createI18n } from "vue-i18n";
import en from "../../src/locales/en.json";

// ...

// You can also share this config with your main.ts file
const i18nOptions = {
  locale: "en",
  fallbackLocale: "en",
  legacy: false,
  messages: {
    en,
  },
};

// ...

// Update the current definition of the "mount" command in Cypress with
Cypress.Commands.add("mount", (component, options = {}) => {
  options.global = options.global || {};
  options.global.plugins = options.global.plugins || [];

  // Create i18n if not provided
  if (!options.i18n) {
    options.i18n = createI18n(i18nOptions);
  } else {
    options.i18n = createI18n(options.i18n);
  }

  // Add i18n plugin
  options.global.plugins.push({
    install(app) {
      app.use(options.i18n);
    },
  });

  return mount(component, options);
});

// ...

By the way, the same process can be applied for Pinia as well, with more information available on the referenced GitHub repository.

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

An issue has been identified in the node_modules/xterm/typings/xterm.d.ts file at line 10, causing an error with code TS1084. The 'reference' directive syntax used

In my project, I have integrated xterm into Angular5. However, I am encountering an error when trying to run the application. Upon executing ng serve, I am facing the following error: ERROR in node_modules/xterm/typings/xterm.d.ts(10,1): error TS1084: In ...

How can Angular2 detect when an entity is clicked within a window?

There are multiple items generated using *ngFor: <my-item *ngFor="let item of myArray" [p]="item"></my-item> I am able to handle a click event like this: <my-item ... (click)="doWork(item)"></my-item> However, I want to avoid a ...

Managing circular dependencies in React useEffect functions

Dealing with a React hook that is in charge of downloading and saving file URLs into a local cache can be quite challenging. The main issue I'm currently facing is finding an effective way to detect changes in the cacheQueue state. Whenever a new item ...

Encountering failures while running Angular tests in GitHub Actions due to reading inner text (which works fine locally)

I am facing an issue in my GitHub actions workflow where Karma is unable to read the 'innerText' of a native element for an Angular project. The error 'TypeError: Cannot read properties of null (reading 'innerText')' is being ...

TypeORM issue - UnsupportedDataTypeError

Here is the entity file I'm working with, user.ts: @Entity('users') export class User { @PrimaryGeneratedColumn() id: number | undefined; @Column({ type: 'string', name: 'username', nullable: true }) username: s ...

Exploring the power of Jasmine with multiple spy functionalities

I'm currently working on writing unit tests for an Angular application using Jasmine, specifically focusing on testing different scenarios within a function. The main challenge I am facing is structuring the test to accommodate various conditions such ...

Using Angular2 to Inject a Service into Another Service

I'm having trouble locating my mistake. app.module.ts ... providers: [ValidateService,AuthService] ... In the register.component.ts file, I perform the following actions: import {AuthService} from '../../services/auth.service' ...

Conceal the message using star symbols

I need help figuring out how to hide a certain type of string input from the user, and then use Angular data binding to display it in another component with part of the data masked with asterisks. I'm not very skilled in JavaScript, so I'm wonder ...

Creating a typescript array with values matching keys in an object: How to do it?

How can I define MyInterfaceKeys in the given code? interface MyInterface extends Record<string, any> { Appearance?: "default" | "primary" | "link"; Size?: "small" | "medium" | "large" ...

I believe there may be a gap in the communication between TypeScript, JavaScript, Angular, Nginx, Alpine, and Docker within the network using Nginx. I am

After transitioning to Docker in order to create a virtual network to mimic a real network (using a bridge type with DNS that resolves the FQDN correctly to the corresponding IP), I encountered the following errors in the console.log - no data is being dis ...

React Project Encounters NPM Installation Failure

I recently started delving into the world of React and experimenting with different examples. Everything was running smoothly until I attempted to start the server [npm start] and encountered an error as shown below. Despite my best efforts, I can't p ...

Exploring the implementation of query parameters in Nest.js

I am currently a freshman in the world of Nest.js. Below is an excerpt from my code: @Get('findByFilter/:params') async findByFilter(@Query() query): Promise<Article[]> { } I have utilized Postman to test this specific router. ht ...

Running asynchronous Jest tests within VueJS lifecycle hooks while utilizing a timer interval

I want to know how to test my "auth/refresh" action in the "beforeCreate" hook using jest as shown below: // main.vue async beforeCreate() { let authTokenRefreshIntervalId; await this.$store.dispatch('auth/initialize'); authTokenR ...

Setting cursor position in input field when navigating with arrow keys: What are the ways to achieve accessibility?

I have implemented arrow key navigation in a table, allowing navigation with the up, down, left, and right arrow keys. How can I ensure that the cursor always stays on the right side of the input field during navigation? Check out my Stackblitz example he ...

In TypeScript, the 'onChange' is declared multiple times, therefore this particular usage will be scrutinized carefully

Within my React project, I am utilizing material-ui, react-hook-form, and Typescript. However, I encountered an error in VSCode when attempting to add the onChange function to a TextField component: 'onChange' is specified more than once, resul ...

Error Encountered When Attempting to Utilize Custom Component on Homepage - Typescript Exception

I'm currently working on creating a unique custom Alert component: type NotificationLevel = "error" | "success" | "info" | "warning" | undefined; export default function CustomNotification(level: NotificationLevel, message: string){ return( ...

utilize the useRef hook to display the total number of characters within a text area

Introducing the following component: import React from 'react'; export interface TexareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { maxLength?: number; id: string; } export const Textarea = React.forwardRef( ( ...

Inserting information into an array: Access the final index consistently with TypeScript

I am interested in dynamically creating a table by allocating it, with the variable nbrBoules: boules:Boule :[] boule:Boule; Boule{id,poids} Method(){ for (var _i = 0; _i < this.nbrBoules; _i++) { this.boule.id = _i; alert(_i); this ...

Importing JSON files dynamically in Typescript with Quasar/Vue using different variable names

Currently, I am in the process of developing an application utilizing the Quasar framework with Typescript. Managing a large number of JSON files for dynamic import has proven to be quite challenging due to the impracticality of manually importing hundreds ...

What is the most efficient method for designing a nested object with two levels of enums, allowing for some enum values to be optional?

As an illustration enum Size { Small: 'smallSize' Medium: 'mediumSize' Large: 'largeSize' } enum Weekday { Monday: 'Monday' Tuesday: 'Tuesday' ... } // Need only Size.Medium and Size.Large, a ...