Repository: metabase/metabase
Pull request #57744: Add more tests to cover missing cases
Author: login: WiNloSt
Labels: name: backport
description: Automatically create PR on current release branch on merge
name: .Team/Embedding
Created: 2025-05-08T15:15:26Z | Merged: 2025-05-14T10:01:51Z

Closes EMB-367

### Description

This PR only add more test cases that were missing from the orignal data picker tests.

### How to verify

All tests pass


### Checklist

- [x] Tests have been added/updated to cover changes in this PR



Diff:
diff --git a/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js b/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
index 054bd7272d58..4d4719e432a9 100644
--- a/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
+++ b/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
@@ -903,6 +903,19 @@ describe("scenarios > embedding > full app", () => {
       });
     }
 
+    /**
+     * Go back from the table selector to the bucket step (where it shows "Raw Data" and "Models").
+     *
+     * This step only shows when there are more than 1 option in the bucket step, the only time this happens
+     * is when there are both selectable models and tables for the current user.
+     */
+    function goBackToBucketStep() {
+      H.popover().within(() => {
+        cy.icon("chevronleft").click();
+        cy.icon("chevronleft").click();
+      });
+    }
+
     beforeEach(() => {
       cy.signInAsNormalUser();
       cy.intercept("GET", "/api/card/*").as("getCard");
@@ -1055,6 +1068,31 @@ describe("scenarios > embedding > full app", () => {
           databaseName: "Sample Database",
         });
       });
+
+      it("should be able to join a model when the data source is a table", () => {
+        const cardDetails = {
+          ...ordersCardDetails,
+          type: "model",
+          collection_id: FIRST_COLLECTION_ID,
+        };
+        H.createQuestion(cardDetails);
+        startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
+        selectTable({
+          tableName: "Products",
+        });
+        H.getNotebookStep("data").button("Join data").click();
+        goBackToBucketStep();
+        selectCard({
+          cardName: cardDetails.name,
+          cardType: "model",
+          collectionNames: ["First collection"],
+        });
+        clickOnJoinDataSource(cardDetails.name);
+        verifyCardSelected({
+          cardName: cardDetails.name,
+          collectionName: "First collection",
+        });
+      });
     });
 
     describe("question", () => {
@@ -1242,31 +1280,65 @@ describe("scenarios > embedding > full app", () => {
         });
       });
 
-      it("should be able to join a card when the data source is a table", () => {
-        const cardDetails = {
-          ...ordersCardDetails,
+      it("should join a table when the data source is a model", () => {
+        // Orders Model already exists
+        const ordersModelName = "Orders Model";
+        const ordersCountModelDetails = {
+          ...ordersCountCardDetails,
+          name: "Orders Count Model",
           type: "model",
-          collection_id: FIRST_COLLECTION_ID,
+          collection_id: null,
         };
-        H.createQuestion(cardDetails);
+        H.createQuestion(ordersCountModelDetails);
+
         startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
-        selectTable({
-          tableName: "Products",
+        selectCard({
+          cardName: ordersModelName,
+          cardType: "model",
+          collectionNames: [],
         });
+
         H.getNotebookStep("data").button("Join data").click();
-        H.popover().within(() => {
-          cy.icon("chevronleft").click();
-          cy.icon("chevronleft").click();
-        });
+        goBackToBucketStep();
         selectCard({
-          cardName: cardDetails.name,
+          cardName: ordersCountModelDetails.name,
           cardType: "model",
-          collectionNames: ["First collection"],
+          collectionNames: [],
         });
-        clickOnJoinDataSource(cardDetails.name);
+
+        cy.log("select join column");
+        H.popover().findByRole("option", { name: "ID" }).click();
+        H.popover().findByRole("option", { name: "Count" }).click();
+
+        clickOnJoinDataSource(ordersCountModelDetails.name);
         verifyCardSelected({
-          cardName: cardDetails.name,
-          collectionName: "First collection",
+          cardName: ordersCountModelDetails.name,
+          collectionName: "Our analytics",
+        });
+      });
+
+      it("should join a model when the data source is a model", () => {
+        // Orders Model already exists
+        const ordersModelName = "Orders Model";
+
+        startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
+        selectCard({
+          cardName: ordersModelName,
+          cardType: "model",
+          collectionNames: [],
+        });
+
+        H.getNotebookStep("data").button("Join data").click();
+        goBackToBucketStep();
+
+        selectTable({
+          tableName: "Products",
+          databaseName: "Sample Database",
+        });
+        clickOnJoinDataSource("Products");
+        verifyTableSelected({
+          tableName: "Products",
+          databaseName: "Sample Database",
         });
       });
     });
diff --git a/enterprise/frontend/src/embedding/data-picker/DataSelector.jsx b/enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.jsx
similarity index 97%
rename from enterprise/frontend/src/embedding/data-picker/DataSelector.jsx
rename to enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.jsx
index e11d60532059..99f7beb02a49 100644
--- a/enterprise/frontend/src/embedding/data-picker/DataSelector.jsx
+++ b/enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.jsx
@@ -24,16 +24,16 @@ import {
   isVirtualCardId,
 } from "metabase-lib/v1/metadata/utils/saved-questions";
 
-import DataBucketPicker from "./DataSelectorDataBucketPicker";
-import DatabasePicker from "./DataSelectorDatabasePicker";
-import DatabaseSchemaPicker from "./DataSelectorDatabaseSchemaPicker";
-import FieldPicker from "./DataSelectorFieldPicker";
-import SchemaPicker from "./DataSelectorSchemaPicker";
-import TablePicker from "./DataSelectorTablePicker";
-import { TableTrigger, Trigger } from "./TriggerComponents";
-import { CONTAINER_WIDTH, DATA_BUCKET } from "./constants";
-import SavedEntityPicker from "./saved-entity-picker/SavedEntityPicker";
-import { getDataTypes } from "./utils";
+import DataBucketPicker from "../DataSelectorDataBucketPicker";
+import DatabasePicker from "../DataSelectorDatabasePicker";
+import DatabaseSchemaPicker from "../DataSelectorDatabaseSchemaPicker";
+import FieldPicker from "../DataSelectorFieldPicker";
+import SchemaPicker from "../DataSelectorSchemaPicker";
+import TablePicker from "../DataSelectorTablePicker";
+import { TableTrigger, Trigger } from "../TriggerComponents";
+import { CONTAINER_WIDTH, DATA_BUCKET } from "../constants";
+import SavedEntityPicker from "../saved-entity-picker/SavedEntityPicker";
+import { getDataTypes } from "../utils";
 
 // chooses a data source bucket (datasets / raw data (tables) / saved questions)
 const DATA_BUCKET_STEP = "BUCKET";
diff --git a/enterprise/frontend/src/embedding/data-picker/DataSelector.module.css b/enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.module.css
similarity index 100%
rename from enterprise/frontend/src/embedding/data-picker/DataSelector.module.css
rename to enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.module.css
diff --git a/enterprise/frontend/src/embedding/data-picker/DataSelector/index.ts b/enterprise/frontend/src/embedding/data-picker/DataSelector/index.ts
new file mode 100644
index 000000000000..cfeea4274fd8
--- /dev/null
+++ b/enterprise/frontend/src/embedding/data-picker/DataSelector/index.ts
@@ -0,0 +1 @@
+export { DataSourceSelector } from "./DataSelector";
diff --git a/enterprise/frontend/src/embedding/data-picker/DataSelector.unit.spec.js b/enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSelector.unit.spec.js
similarity index 99%
rename from enterprise/frontend/src/embedding/data-picker/DataSelector.unit.spec.js
rename to enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSelector.unit.spec.js
index 8a7dd07a8a5b..d78161da043c 100644
--- a/enterprise/frontend/src/embedding/data-picker/DataSelector.unit.spec.js
+++ b/enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSelector.unit.spec.js
@@ -9,7 +9,7 @@ import {
   createSampleDatabase,
 } from "metabase-types/api/mocks/presets";
 
-import { UnconnectedDataSelector as DataSelector } from "./DataSelector";
+import { UnconnectedDataSelector as DataSelector } from "../DataSelector";
 
 const MULTI_SCHEMA_DB_ID = 2;
 const MULTI_SCHEMA_TABLE1_ID = 100;
diff --git a/enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx b/enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
new file mode 100644
index 000000000000..cc50b621e4a5
--- /dev/null
+++ b/enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
@@ -0,0 +1,210 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number;
+    databaseId: number;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable,
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable ? selectedTable.databaseId : null}
+      selectedTableId={selectedTable ? selectedTable.id : undefined}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  it("should close the picker when clicking outside", async () => {
+    setup({
+      availableModels: "tables-only",
+    });
+
+    expect(await screen.findByText("Orders")).toBeInTheDocument();
+    expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+    await userEvent.click(
+      screen.getByText("Click me to open or close data picker"),
+    );
+
+    expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+    expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+  });
+
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.getByText("Table 2")).toBeInTheDocument();
+      expect(screen.getByText("Table 3")).toBeInTheDocument();
+      expect(screen.getByText("Table 4")).toBeInTheDocument();
+      expect(screen.getByText("Table 5")).toBeInTheDocument();
+      expect(screen.getByText("Table 6")).toBeInTheDocument();
+      expect(screen.getByText("Table 7")).toBeInTheDocument();
+      expect(screen.getByText("Table 8")).toBeInTheDocument();
+      expect(screen.getByText("Table 9")).toBeInTheDocument();
+      expect(screen.getByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(screen.getByText("Table 10")).toBeInTheDocument();
+    });
+
+    it("should not show search input when there less than 10 tables", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      expect(screen.queryByPlaceholderText("Find...")).not.toBeInTheDocument();
+    });
+  });
+
+  describe("both models and tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-and-models",
+    };
+
+    it("should only show data from the selected database when joining data", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      const manyTablesDatabase = createMockDatabase({
+        id: DB_ID,
+        name: "Many tables Database",
+        tables: [...createNNumberOfTables(10, DB_ID)],
+      });
+
+      setup({
+        ...setupOpts,
+        isJoinStep: true,
+        databases: [sampleDatabase, manyTablesDatabase],
+        selectedTable: {
+          id: (manyTablesDatabase.tables as Table[])[0].id as number,
+          databaseId: manyTablesDatabase.id,
+        },
+      });
+
+      expect(
+        await screen.findByText("Many tables Database"),
+      ).toBeInTheDocument();
+      expect(screen.getByText("Table 1")).toBeInTheDocument();
+      await userEvent.click(screen.getByText("Many tables Database"));
+      // We're at the database step
+      expect(await screen.findByText("Raw Data")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+  });
+});
+
+function createNNumberOfTables(numberOfTables: number, dbId: number): Table[] {
+  // To avoid having the same ID as the sample database tables
+  const STARTING_TABLE_ID = 100;
+  return Array.from({ length: numberOfTables }, (_, index) =>
+    createMockTable({
+      id: getNextId(index === 0 ? STARTING_TABLE_ID : undefined),
+      db_id: dbId,
+      display_name: `Table ${index + 1}`,
+    }),
+  );
+}
diff --git a/enterprise/frontend/src/embedding/data-picker/RawDataBackButton.tsx b/enterprise/frontend/src/embedding/data-picker/RawDataBackButton.tsx
index 9a9be3dff1b9..41d0a75cbd39 100644
--- a/enterprise/frontend/src/embedding/data-picker/RawDataBackButton.tsx
+++ b/enterprise/frontend/src/embedding/data-picker/RawDataBackButton.tsx
@@ -3,7 +3,7 @@ import { t } from "ttag";
 import CS from "metabase/css/core/index.css";
 import { Box, Flex, Icon } from "metabase/ui";
 
-import DataSelectorS from "./DataSelector.module.css";
+import DataSelectorS from "./DataSelector/DataSelector.module.css";
 
 export function RawDataBackButton() {
   return (
diff --git a/enterprise/frontend/src/embedding/data-picker/TriggerComponents.tsx b/enterprise/frontend/src/embedding/data-picker/TriggerComponents.tsx
index 64c4d1f70ca3..526b8f13dd9f 100644
--- a/enterprise/frontend/src/embedding/data-picker/TriggerComponents.tsx
+++ b/enterprise/frontend/src/embedding/data-picker/TriggerComponents.tsx
@@ -9,7 +9,7 @@ import type Database from "metabase-lib/v1/metadata/Database";
 import type Field from "metabase-lib/v1/metadata/Field";
 import type Table from "metabase-lib/v1/metadata/Table";
 
-import DataSelectorS from "./DataSelector.module.css";
+import DataSelectorS from "./DataSelector/DataSelector.module.css";
 
 export function Trigger({
   className,
diff --git a/frontend/src/metabase/embedding-sdk/types/components/data-picker.ts b/frontend/src/metabase/embedding-sdk/types/components/data-picker.ts
index 04973305e9e9..c675128b97d9 100644
--- a/frontend/src/metabase/embedding-sdk/types/components/data-picker.ts
+++ b/frontend/src/metabase/embedding-sdk/types/components/data-picker.ts
@@ -2,10 +2,11 @@ import type { TableId } from "metabase-types/api";
 
 export interface DataSourceSelectorProps {
   isInitiallyOpen: boolean;
+  /** false when joining data, true otherwise */
   canChangeDatabase: boolean;
   selectedDatabaseId: number | null;
-  selectedTableId: TableId | undefined;
-  selectedCollectionId: number | null | undefined;
+  selectedTableId?: TableId;
+  selectedCollectionId?: number | null;
   canSelectModel: boolean;
   canSelectTable: boolean;
   triggerElement: JSX.Element;
diff --git a/frontend/test/__support__/utils.ts b/frontend/test/__support__/utils.ts
index 7b907f529fb4..f2aabd9cae48 100644
--- a/frontend/test/__support__/utils.ts
+++ b/frontend/test/__support__/utils.ts
@@ -4,7 +4,12 @@ import { act, waitFor } from "./ui";
 
 export const getNextId = (() => {
   let id = 0;
-  return () => ++id;
+  return (startingId?: number) => {
+    if (startingId) {
+      id = startingId;
+    }
+    return ++id;
+  };
 })();
 
 export async function delay(duration: number) {


Reviews:
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T14:53:34Z
user: login: heypoom
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-13T13:16:06Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T13:17:33Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:00:25Z
user: login: heypoom
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T00:47:54Z

Review comments:
diff_hunk: @@ -1242,31 +1270,71 @@ describe("scenarios > embedding > full app", () => {
         });
       });
 
-      it("should be able to join a card when the data source is a table", () => {
-        const cardDetails = {
-          ...ordersCardDetails,
+      it("should join a table when the data source is a model", () => {
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: WiNloSt
body: ok, so the diff is super funny. Anyways, the test that were here was moved up above since I put it in the wrong place. It was a table joining a model, so the test should be under `"table"` not `"model"`


And 2 new tests were added.
1. model joins table
2. model joins model
created_at: 2025-05-09T14:18:09Z
author_association: MEMBER
diff_hunk: @@ -44,6 +44,7 @@ const SCHEMA_STEP = "SCHEMA";
 // chooses a table (database has already been selected)
 const TABLE_STEP = "TABLE";
 // chooses a table field (table has already been selected)
+// XXX: This could be removed since embedding data picker doesn't show fields
path: enterprise/frontend/src/embedding/data-picker/DataSelector/DataSelector.jsx
user: login: WiNloSt
body: I'll create another PR for this.
created_at: 2025-05-09T14:18:27Z
author_association: MEMBER
diff_hunk: @@ -9,7 +9,7 @@ import {
   createSampleDatabase,
 } from "metabase-types/api/mocks/presets";
 
-import { UnconnectedDataSelector as DataSelector } from "./DataSelector";
+import { UnconnectedDataSelector as DataSelector } from "../DataSelector";
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSelector.unit.spec.js
user: login: WiNloSt
body: so I moved `DataSelector` under its own directory, so I could add `tests` directory which would hold a new TypeScript test file since I don't wanna add new tests in `DataSelector.unit.spec.js` since typing doesn't work there.
created_at: 2025-05-09T14:22:17Z
author_association: MEMBER
diff_hunk: @@ -3,7 +3,7 @@ import { t } from "ttag";
 import CS from "metabase/css/core/index.css";
 import { Box, Flex, Icon } from "metabase/ui";
 
-import DataSelectorS from "./DataSelector.module.css";
+import DataSelectorS from "./DataSelector/DataSelector.module.css";
path: enterprise/frontend/src/embedding/data-picker/RawDataBackButton.tsx
user: login: WiNloSt
body: just some folder movements.
created_at: 2025-05-09T14:22:34Z
author_association: MEMBER
diff_hunk: @@ -2,10 +2,11 @@ import type { TableId } from "metabase-types/api";
 
 export interface DataSourceSelectorProps {
   isInitiallyOpen: boolean;
+  /** false when joining data, true otherwise */
   canChangeDatabase: boolean;
   selectedDatabaseId: number | null;
-  selectedTableId: TableId | undefined;
-  selectedCollectionId: number | null | undefined;
+  selectedTableId?: TableId;
+  selectedCollectionId?: number | null;
path: frontend/src/metabase/embedding-sdk/types/components/data-picker.ts
user: login: WiNloSt
body: make them optional, they already included `undefined` previously, but doing this will make unit testing easier.
created_at: 2025-05-09T14:23:06Z
author_association: MEMBER
diff_hunk: @@ -4,7 +4,12 @@ import { act, waitFor } from "./ui";
 
 export const getNextId = (() => {
   let id = 0;
-  return () => ++id;
+  return (startingId?: number) => {
path: frontend/test/__support__/utils.ts
user: login: WiNloSt
body: This is needed in my case. There was no way to set the starting ID. So it only started with 0, and that could clash with a lot of pre-existing entities.
created_at: 2025-05-09T14:23:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,185 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={null}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: This file basically tests the [main use cases defined here](https://www.notion.so/metabase/Test-Extract-embedding-picker-out-of-the-current-data-picker-1e469354c90180379ea1c3160cfe56b7?pvs=4#1e469354c90180b39bffc0d339c4b04f)
created_at: 2025-05-09T14:24:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,185 @@
+import userEvent from "@testing-library/user-event";
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: I initially wanted to add this to `DataSelector.unit.spec.js`, but it was a JS file, so the type wasn't working. I then create a new TS file instead.
created_at: 2025-05-09T14:25:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,185 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: there's no `models-only` situation because models are created from `table` so if `dataset` is available, it means `table` is also available.
created_at: 2025-05-09T14:25:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(await screen.findByText("Table 2")).toBeInTheDocument();
+      expect(await screen.findByText("Table 3")).toBeInTheDocument();
+      expect(await screen.findByText("Table 4")).toBeInTheDocument();
+      expect(await screen.findByText("Table 5")).toBeInTheDocument();
+      expect(await screen.findByText("Table 6")).toBeInTheDocument();
+      expect(await screen.findByText("Table 7")).toBeInTheDocument();
+      expect(await screen.findByText("Table 8")).toBeInTheDocument();
+      expect(await screen.findByText("Table 9")).toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+    });
+
+    it("should not show search input when there less than 10 tables", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      expect(screen.queryByPlaceholderText("Find...")).not.toBeInTheDocument();
+    });
+  });
+
+  describe("both models and tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-and-models",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+
+      expect(await screen.findByText("Models")).toBeInTheDocument();
+      expect(screen.getByText("Raw Data")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Models")).not.toBeInTheDocument();
+      expect(screen.queryByText("Raw Data")).not.toBeInTheDocument();
+    });
+
+    it("should only show data from the selected database when joining data", async () => {
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: I don't need to repeat this inside `tables` only. I mean I could but when we have more case everything would be doubled.
created_at: 2025-05-09T14:52:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: this is one of the edge cases I found. If we find more of these cases in the future, we could add more tests here.

Here's where it's implemented
https://github.com/metabase/metabase/blob/dff0c24b0bdff1286d58e65162e4072e24f6116e/enterprise/frontend/src/embedding/data-picker/DataSelectorTablePicker/DataSelectorTablePicker.tsx#L55
created_at: 2025-05-09T14:53:21Z
author_association: MEMBER
diff_hunk: @@ -1055,6 +1055,34 @@ describe("scenarios > embedding > full app", () => {
           databaseName: "Sample Database",
         });
       });
+
+      it("should be able to join a model when the data source is a table", () => {
+        const cardDetails = {
+          ...ordersCardDetails,
+          type: "model",
+          collection_id: FIRST_COLLECTION_ID,
+        };
+        H.createQuestion(cardDetails);
+        startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
+        selectTable({
+          tableName: "Products",
+        });
+        H.getNotebookStep("data").button("Join data").click();
+        H.popover().within(() => {
+          cy.icon("chevronleft").click();
+          cy.icon("chevronleft").click();
+        });
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: heypoom
body: Perhaps a `cy.log` here would be helpful, not exactly sure what this does
created_at: 2025-05-13T10:36:22Z
author_association: MEMBER
diff_hunk: @@ -1242,31 +1270,71 @@ describe("scenarios > embedding > full app", () => {
         });
       });
 
-      it("should be able to join a card when the data source is a table", () => {
-        const cardDetails = {
-          ...ordersCardDetails,
+      it("should join a table when the data source is a model", () => {
+        // Orders Model already exists
+        const ordersModelName = "Orders Model";
+        const ordersCountModelDetails = {
+          ...ordersCountCardDetails,
+          name: "Orders Count Model",
           type: "model",
-          collection_id: FIRST_COLLECTION_ID,
+          collection_id: null,
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: heypoom
body: Just curious why we changed from the first collection to Our analytics here
created_at: 2025-05-13T13:10:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: heypoom
body: since the above uses `?`

```suggestion
    id?: number;
```
created_at: 2025-05-13T13:11:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(await screen.findByText("Table 2")).toBeInTheDocument();
+      expect(await screen.findByText("Table 3")).toBeInTheDocument();
+      expect(await screen.findByText("Table 4")).toBeInTheDocument();
+      expect(await screen.findByText("Table 5")).toBeInTheDocument();
+      expect(await screen.findByText("Table 6")).toBeInTheDocument();
+      expect(await screen.findByText("Table 7")).toBeInTheDocument();
+      expect(await screen.findByText("Table 8")).toBeInTheDocument();
+      expect(await screen.findByText("Table 9")).toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: heypoom
body: why does Table 2 - 9 not have an await, while the above do?
created_at: 2025-05-13T13:13:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(await screen.findByText("Table 2")).toBeInTheDocument();
+      expect(await screen.findByText("Table 3")).toBeInTheDocument();
+      expect(await screen.findByText("Table 4")).toBeInTheDocument();
+      expect(await screen.findByText("Table 5")).toBeInTheDocument();
+      expect(await screen.findByText("Table 6")).toBeInTheDocument();
+      expect(await screen.findByText("Table 7")).toBeInTheDocument();
+      expect(await screen.findByText("Table 8")).toBeInTheDocument();
+      expect(await screen.findByText("Table 9")).toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+    });
+
+    it("should not show search input when there less than 10 tables", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      expect(screen.queryByPlaceholderText("Find...")).not.toBeInTheDocument();
+    });
+  });
+
+  describe("both models and tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-and-models",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+
+      expect(await screen.findByText("Models")).toBeInTheDocument();
+      expect(screen.getByText("Raw Data")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Models")).not.toBeInTheDocument();
+      expect(screen.queryByText("Raw Data")).not.toBeInTheDocument();
+    });
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: heypoom
body: I would move this out of the `describe("both models and tables are available"` block since the ability to close the picker doesn't really depend on what entities are available.
created_at: 2025-05-13T13:14:45Z
author_association: MEMBER
diff_hunk: @@ -4,7 +4,12 @@ import { act, waitFor } from "./ui";
 
 export const getNextId = (() => {
   let id = 0;
-  return () => ++id;
+  return (startingId?: number) => {
path: frontend/test/__support__/utils.ts
user: login: heypoom
body: do we have a test for the `getNextId` utility?
created_at: 2025-05-13T13:16:03Z
author_association: MEMBER
diff_hunk: @@ -4,7 +4,12 @@ import { act, waitFor } from "./ui";
 
 export const getNextId = (() => {
   let id = 0;
-  return () => ++id;
+  return (startingId?: number) => {
path: frontend/test/__support__/utils.ts
user: login: WiNloSt
body: I couldn't find one. I assume we don't have it.
created_at: 2025-05-13T13:17:33Z
author_association: MEMBER
diff_hunk: @@ -1055,6 +1055,34 @@ describe("scenarios > embedding > full app", () => {
           databaseName: "Sample Database",
         });
       });
+
+      it("should be able to join a model when the data source is a table", () => {
+        const cardDetails = {
+          ...ordersCardDetails,
+          type: "model",
+          collection_id: FIRST_COLLECTION_ID,
+        };
+        H.createQuestion(cardDetails);
+        startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
+        selectTable({
+          tableName: "Products",
+        });
+        H.getNotebookStep("data").button("Join data").click();
+        H.popover().within(() => {
+          cy.icon("chevronleft").click();
+          cy.icon("chevronleft").click();
+        });
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: WiNloSt
body: That's a really good point. I think rather than using `cy.log` it's better to extract this to a meaningful function.
created_at: 2025-05-13T15:33:04Z
author_association: MEMBER
diff_hunk: @@ -1242,31 +1270,71 @@ describe("scenarios > embedding > full app", () => {
         });
       });
 
-      it("should be able to join a card when the data source is a table", () => {
-        const cardDetails = {
-          ...ordersCardDetails,
+      it("should join a table when the data source is a model", () => {
+        // Orders Model already exists
+        const ordersModelName = "Orders Model";
+        const ordersCountModelDetails = {
+          ...ordersCountCardDetails,
+          name: "Orders Count Model",
           type: "model",
-          collection_id: FIRST_COLLECTION_ID,
+          collection_id: null,
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: WiNloSt
body: We did not change anything here as per my [comment above](https://github.com/metabase/metabase/pull/57744/files/2d68693dbdd88f370172b595da266955806c9ebf#r2081788404). The diff is just being funny. This is a completely new test.

The test name isn't even the same as the one before 😅 
created_at: 2025-05-13T15:48:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(await screen.findByText("Table 2")).toBeInTheDocument();
+      expect(await screen.findByText("Table 3")).toBeInTheDocument();
+      expect(await screen.findByText("Table 4")).toBeInTheDocument();
+      expect(await screen.findByText("Table 5")).toBeInTheDocument();
+      expect(await screen.findByText("Table 6")).toBeInTheDocument();
+      expect(await screen.findByText("Table 7")).toBeInTheDocument();
+      expect(await screen.findByText("Table 8")).toBeInTheDocument();
+      expect(await screen.findByText("Table 9")).toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+    });
+
+    it("should not show search input when there less than 10 tables", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      expect(screen.queryByPlaceholderText("Find...")).not.toBeInTheDocument();
+    });
+  });
+
+  describe("both models and tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-and-models",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+
+      expect(await screen.findByText("Models")).toBeInTheDocument();
+      expect(screen.getByText("Raw Data")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Models")).not.toBeInTheDocument();
+      expect(screen.queryByText("Raw Data")).not.toBeInTheDocument();
+    });
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: well you have a point. Fixed in cd3ff1223b81e7d0350ff02cb7edc29d111e9217
  

created_at: 2025-05-13T15:58:48Z
author_association: MEMBER
diff_hunk: @@ -1055,6 +1055,34 @@ describe("scenarios > embedding > full app", () => {
           databaseName: "Sample Database",
         });
       });
+
+      it("should be able to join a model when the data source is a table", () => {
+        const cardDetails = {
+          ...ordersCardDetails,
+          type: "model",
+          collection_id: FIRST_COLLECTION_ID,
+        };
+        H.createQuestion(cardDetails);
+        startNewEmbeddingQuestion({ isMultiStageDataPicker: true });
+        selectTable({
+          tableName: "Products",
+        });
+        H.getNotebookStep("data").button("Join data").click();
+        H.popover().within(() => {
+          cy.icon("chevronleft").click();
+          cy.icon("chevronleft").click();
+        });
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: WiNloSt
body: Fixed in 9bc8b37b58faf3667d86a5fe92691b4cfc725f02
created_at: 2025-05-13T16:00:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: Well, I did actually want it to be
```ts
  selectedTable?: {
    id: number;
    databaseId: number;
  };
```

But that means I couldn't set a default value to it. Since I expect people to pass this option to pass every property when passing `selectedTable`.

I fixed it another way in b781689c83dabaf7a901e513e0d4e7fa1a9131c5

created_at: 2025-05-13T16:00:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,224 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { getNextId } from "__support__/utils";
+import type { Database, Table } from "metabase-types/api";
+import { createMockDatabase, createMockTable } from "metabase-types/api/mocks";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+
+import { DataSourceSelector } from "../DataSelector";
+
+const DATABASES = [createSampleDatabase()];
+
+const storeInitialState = createMockState({
+  settings: createMockSettingsState({
+    "enable-nested-queries": true,
+  }),
+});
+
+const AVAILABLE_MODELS: Record<AvailableModels, ("dataset" | "table")[]> = {
+  "tables-only": ["table"],
+  "tables-and-models": ["dataset", "table"],
+};
+
+type AvailableModels = "tables-only" | "tables-and-models";
+
+interface SetupOpts {
+  databases?: Database[];
+  isJoinStep?: boolean;
+  availableModels?: AvailableModels;
+  selectedTable?: {
+    id: number | undefined;
+    databaseId: number | null;
+  };
+}
+
+function setup({
+  databases = DATABASES,
+  isJoinStep = false,
+  availableModels = "tables-only",
+  selectedTable = {
+    id: undefined,
+    databaseId: null,
+  },
+}: SetupOpts = {}) {
+  fetchMock.get(
+    {
+      url: "path:/api/search",
+      query: {
+        calculate_available_models: true,
+        limit: 0,
+        models: ["dataset"],
+      },
+    },
+    {
+      data: [],
+      limit: 0,
+      models: ["dataset"],
+      offset: 0,
+      table_db_id: null,
+      engine: "search.engine/in-place",
+      total: 1,
+      available_models: AVAILABLE_MODELS[availableModels],
+    },
+  );
+
+  setupDatabasesEndpoints(databases, undefined, { saved: true });
+
+  return renderWithProviders(
+    <DataSourceSelector
+      isInitiallyOpen
+      canChangeDatabase={!isJoinStep}
+      selectedDatabaseId={selectedTable.databaseId}
+      selectedTableId={selectedTable.id}
+      canSelectModel={true}
+      canSelectTable={true}
+      triggerElement={<div>Click me to open or close data picker</div>}
+      setSourceTableFn={jest.fn()}
+    />,
+    { storeInitialState },
+  );
+}
+
+describe("DataSourceSelector", () => {
+  describe("only tables are available", () => {
+    const setupOpts: SetupOpts = {
+      availableModels: "tables-only",
+    };
+
+    it("should close the picker when clicking outside", async () => {
+      setup(setupOpts);
+      expect(await screen.findByText("Orders")).toBeInTheDocument();
+      expect(screen.getByText("Sample Database")).toBeInTheDocument();
+
+      await userEvent.click(
+        screen.getByText("Click me to open or close data picker"),
+      );
+
+      expect(screen.queryByText("Orders")).not.toBeInTheDocument();
+      expect(screen.queryByText("Sample Database")).not.toBeInTheDocument();
+    });
+
+    it("should show search input when there are 10 and more tables", async () => {
+      const sampleDatabase = createSampleDatabase();
+      const DB_ID = getNextId(sampleDatabase.id);
+      setup({
+        ...setupOpts,
+        databases: [
+          sampleDatabase,
+          createMockDatabase({
+            id: DB_ID,
+            name: "Many tables Database",
+            tables: [...createNNumberOfTables(10, DB_ID)],
+          }),
+        ],
+      });
+      expect(await screen.findByText("Sample Database")).toBeInTheDocument();
+      expect(screen.getByText("Many tables Database")).toBeInTheDocument();
+
+      await userEvent.click(await screen.findByText("Many tables Database"));
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(await screen.findByText("Table 2")).toBeInTheDocument();
+      expect(await screen.findByText("Table 3")).toBeInTheDocument();
+      expect(await screen.findByText("Table 4")).toBeInTheDocument();
+      expect(await screen.findByText("Table 5")).toBeInTheDocument();
+      expect(await screen.findByText("Table 6")).toBeInTheDocument();
+      expect(await screen.findByText("Table 7")).toBeInTheDocument();
+      expect(await screen.findByText("Table 8")).toBeInTheDocument();
+      expect(await screen.findByText("Table 9")).toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
+
+      const searchBox = screen.getByPlaceholderText("Find...");
+      expect(searchBox).toBeInTheDocument();
+
+      // Assert that the search query is used.
+      await userEvent.type(searchBox, "Table 1");
+      expect(await screen.findByText("Table 1")).toBeInTheDocument();
+      expect(screen.queryByText("Table 2")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 3")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 4")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 5")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 6")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 7")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 8")).not.toBeInTheDocument();
+      expect(screen.queryByText("Table 9")).not.toBeInTheDocument();
+      expect(await screen.findByText("Table 10")).toBeInTheDocument();
path: enterprise/frontend/src/embedding/data-picker/DataSelector/tests/DataSourceSelector.unit.spec.tsx
user: login: WiNloSt
body: since they're using `queryBy` they don't need to be `await`ed. Anyway, we're supposed to only `await` one element to make sure everything is loaded.

Fixed by 1112ab5373e45f4d99aa34ee1693cdc232034212

created_at: 2025-05-13T16:00:18Z
author_association: MEMBER
diff_hunk: @@ -1242,31 +1270,71 @@ describe("scenarios > embedding > full app", () => {
         });
       });
 
-      it("should be able to join a card when the data source is a table", () => {
-        const cardDetails = {
-          ...ordersCardDetails,
+      it("should join a table when the data source is a model", () => {
+        // Orders Model already exists
+        const ordersModelName = "Orders Model";
+        const ordersCountModelDetails = {
+          ...ordersCountCardDetails,
+          name: "Orders Count Model",
           type: "model",
-          collection_id: FIRST_COLLECTION_ID,
+          collection_id: null,
path: e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
user: login: heypoom
body: ah! I thought using split diffs would making it clearer, turns out it's still confusing 😆 
created_at: 2025-05-14T00:47:54Z
author_association: MEMBER

PR comments:
user: login: trunk-io[bot]
created_at: 2025-05-08T17:11:12Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://raster.shields.io/badge/14720_tests-cd3ff122-purple)](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment) &ensp; [![Static Badge](https://raster.shields.io/badge/1-failed-crimson)](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-flaky-red)](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment&runConclusion=QUARANTINED)


| Failed Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>admin > permissions > sandboxes (tested via the API) Sandboxing reproductions unsaved/dirty query should work on linked table column with multiple...</code> | The column "PRODUCTS\_\_via\_\_PRODUCT\_ID.USER\_ID" was not found in the SQL query. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment&runConclusion=FAILURE&runId=9a3641bb-a24c-5e61-9d56-869942780be4) |
<sub>

[View Full Report ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57744?repo=metabase/metabase&commitHash=cd3ff1223b81e7d0350ff02cb7edc29d111e9217&utm_source=gh-comment) ⋅ [Docs](https://docs.trunk.io/flaky-tests?utm_source=gh-comment)

</sub>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-13T16:20:04Z
body: 
  ## e2e tests failed on `cd3ff1223b81e7d0350ff02cb7edc29d111e9217-1`

  [e2e test run](https://github.com/metabase/metabase/actions/runs/15001287475?pr=57744)

  File | Test Name
  ---- | ---------
  `sandboxing-via-api.cy.spec.js` | (_flaky_) admin > permissions > sandboxes (tested via the API) > Sandboxing reproductions > unsaved/dirty query should work on linked table column with multiple dimensions and remapping (metabase#15106)

author_association: CONTRIBUTOR