playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] Component as slot

Open sand4rt opened this issue 3 years ago • 7 comments

I cannot specify a component as a slot:

import { test, expect } from '@playwright/experimental-ct-vue';
import Form from './Form.vue';

test('renders a component as slot', async ({ mount }) => {
    const component = await mount(Form, {
        slots: {
            // Input is registered in playwright/index.ts. 
            default: `<Input />`
        }
    });

    await expect(component.getByRole('textbox')).toBeVisible(); // input cannot be found
});

TODO

  • [ ] Svelte component as slot (blocked until: https://github.com/sveltejs/svelte/issues/2588 is resolved)
  • [x] Vue3 component as slot
  • [ ] Vue2 component as slot

sand4rt avatar Jul 24 '22 10:07 sand4rt

Yes, I'm not sure I understand the text vs markup ambiguity in VTU, so we interpret slots as text there. Any reason you don't like JSX for composition? Looks way easier to read, is more in line with the way you compose in the actual .vue files. Would be interested in what you think.

pavelfeldman avatar Jul 25 '22 21:07 pavelfeldman

I agree with your statements. However, my thought process is that it's another technology that adds to an already large stack of technologies that people are being pushed towards when using frontend frameworks.

In my opinion there aren't many use cases where you want to add complex slots to your component (inside tests). I only do this when the parent component and child components are talking directly to each other which is rare. So the need for JSX is less.

In addition, the API of JSX and the Vue template engine are not quite the same. In my opinion, those minor differences are confusing to people unfamiliar with JSX and most people using Vue don't use JSX. You probably know the differences but those are some of them:


Vue <button @click="onButtonClick /> JSX <button onClick={onButtonClick} />


Vue <Counter :count="count" /> JSX <Counter count={count} />


Vue <div>{{ text }}</div> JSX <div>{ text }</div>


Vue <input v-model="inputValue" /> JSX Not sure if using v-model is possible with JSX? Will probably look like this?: <input onChange={handleChange} value={inputValue} />


With that being said the mount options API isn't perfect either. As you said, the code does not look like how you would compose them in .vue files. Also no type checking and syntax highlighting when using HTML in slots. So at this time for me it's a matter of "pick your poison". Would love to see the full Vue template syntax working in JSX but don't think this is possible?

sand4rt avatar Jul 26 '22 14:07 sand4rt

Would love to see the full Vue template syntax working in JSX but don't think this is possible?

All the Vue examples you provide above are actually a subset of JSX, at least syntactically. {{ text }} is basically an object shorthand notation { text } that you can use inside the code block { }. But the tooling / type checking will get confused for sure, so I would keep it JSX for the VS Code smarts.

The missing bits for the complete template support in slots would be:

  • The mapping from the component name in the markup to the actual component definition. Probably discoverable from the registry of components.
  • Actual rendering call - we don't want to interpret this string, need some Vue internals to do that for us.

pavelfeldman avatar Jul 26 '22 20:07 pavelfeldman

I see that Vue 3 as a slot is done, but I can not make it to work. I see @sand4rt in your change, that you register the component globally in the app. But what If I don't want to register it globally, but just want to use it in one test as a slot? It doesn't render anything in that scenario. Something like that:

const component = await mount(WizardTestWrapper, {
      props: {
        model: {}
      },
      components: {
        WizardFirstPage,
      },
      slots: {
        default: "<WizardFirstPage/>",
      },
    });

pbrzosko avatar Mar 13 '24 07:03 pbrzosko

Hi @pbrzosko, i usually just register it globally, but if you want to register the component for one single test you could use:

// playwright/index.ts
export type HooksConfig = {
  registerButton: boolean;
}

beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
  for (hooksConfig.registerButton)
    app.component('button', Button);
});
// slots.test.ts
test('render a component as slot', async ({ mount }) => {
  const component = await mount<HooksConfig>(DefaultSlot, {
    slots: {
      default: '<Button title="Submit" />',
    },
    hooksConfig: {
      registerButton: true
    }
  });
  await expect(component).toContainText('Submit');
});

This is would be nice to have I think, but is not suported ATM:

// playwright/index.ts
export type HooksConfig = {
  components: Record<any, any>;
}

beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
  for (const [name, component] of Object.entries(hooksConfig.components))
    app.component(name, component);
});
// slots.test.ts
test('render a component as slot', async ({ mount }) => {
  const component = await mount<HooksConfig>(DefaultSlot, {
    slots: {
      default: '<Button title="Submit" />',
    },
    hooksConfig: {
      components: { Button }
    }
  });
  await expect(component).toContainText('Submit');
});

sand4rt avatar Mar 13 '24 17:03 sand4rt

But I believe there is a components object in mount options. Why it is not enough to specify a component there?

pbrzosko avatar Mar 13 '24 19:03 pbrzosko