John
John

Reputation: 1773

writing to svelte writable store value from cypress

Within a Svelte app, I have a boolean variable.

import { writable } from 'svelte/store'
export const authorised = writable(false)

This is imported into App.svelte and other svelte files and accessed and updated using $authorised.

I can't use the $ syntax in Cypress so I used

import { authorised } from '../../src/stores'
describe( etc etc
        authorised.set(true)
        cy.visit(`/`)

Within App.svelte, I have a console.log showing value of $authorised.

Using dev tools in the cypress browser output, authorised is showing false.

The test logs in via the backend api, receiving the token and user id in the body response. With this data I set various values in svelte store (including authorised) so that when visit the app, it should show the authorised screen rather than the login screen.

So why is authorised not being set in svelte store?

Updated:

Using Fody's approach, I added to the start of App.svelte, outside of script,

declare global {
  interface Window {
    Cypress?: Cypress.Cypress;
  }
}

Putting this within script gives an error on declare. But the above code, gives an error on interface.

The keyword 'interface' is reserved

I have looked at Parsing error: The keyword 'interface' is reserved and am installing ts-standard and updating the package.json. But the install is taking a long time.

Am I supposed to be installing ts-standard?

Further update:

In main.ts, I added

import type { Writable } from 'svelte/store';    
declare global {
        interface Window {
          Cypress?: Cypress.Cypress
          authorised?: Writable<boolean>
        }
    }

And in App.svelte.

  if (window.Cypress) {
    window.authorised.set($authorised)          
  }

and finally in the test.js file

.its('body').then((body) => {
        cy.log(body)
        cy.visit(`/`)
        cy.window().then(win => win.authorised.set(true))

Running the test shows an error of "windows.authorised is undefined" with a console log of Uncaught TypeError: window.authorised is undefined instance App.svelte:16 init index.mjs:1891 App bundle.js:4033 app main.ts:14 bundle.js:4050

where App.svelte:16 is "window.authorised.set($authorised) "

Upvotes: 1

Views: 831

Answers (2)

John
John

Reputation: 1773

What I got working was a mixture of @fody's answer and mine. So in main.ts - I added the additions to the Window interface so that cypress and the app can communicate via this. In App.svelte - I added (the variables preceeded by $ are svelte store variables)

 if (window.Cypress) {
    console.log("entering Cypress selection")
    authorised = window.authorised
    $emailName = window.emailName
    $authToken = window.authToken
    $userId = window.userId
  }

And in the test

  cy.request('POST', Cypress.env('api')+'users/login', {
    email: "[email protected]",
    password: "12345678"
  }).its('body').then((body) => {
            cy.log(body)

            cy.visit(`/`, {
              onBeforeLoad(win) {
                win.authorised = true
                win.emailName = "[email protected]"
                win.authToken = body.token
                win.userId = body.userId
              },
            })
                                    
          cy.get('h1').should('contain', 'Svelte To-Do List')       

So the key bit is the onBeforeLoad as that updates the new app window with the auth information. Without this, there are two different window objects and information is not passed across.

Upvotes: 0

Fody
Fody

Reputation: 32080

If you import the writable store directly, it's not using the same instance as the one in the app.

But you can attach it as a property of window to allow Cypress access to the same instance.

A minimal example:

Svelte app

<script>
  import { writable } from 'svelte/store'

  export const authorised = writable(false)

  if (window.Cypress) {
    window.authorised = authorised;          // only if Cypress is running
  }

  let authorisedValue;
  authorised.subscribe(value => {
    authorisedValue = value;
  });
</script>

<main>
  <div id="app-container" class="app-container">
    <div id="authorised-display-value">{authorisedValue}</div>
  </div>
</main>

Test

it('passes', () => {
  cy.visit('http://localhost:8080/')
  cy.get('#authorised-display-value').should('contain', 'false')    // passes
  cy.window().then(win => win.authorised.set(true))
  cy.get('#authorised-display-value').should('contain', 'true')     // passes
})

Typescript support

If the project is typescript-based, the window.Cypress and window.authorised references will give a problem.

Define these in main.ts.

For my minimal app:

import type { Writable } from 'svelte/store';
import App from './App.svelte';

declare global {
  interface Window {
    Cypress?: Cypress.Cypress;
    authorised?: Writable<Boolean>;
  }
}

const app = new App({
    target: document.body
});

export default app;

It's also good practice to add a generic parameter to the writable store.

In App.svelte:

<script lang="ts">
  import { writable } from 'svelte/store'
  export const authorised = writable<Boolean>(false)
  ...

Finally, define the global type imports in global.d.ts:

/// <reference types="svelte" />
/// <reference types="cypress" />

Upvotes: 1

Related Questions