diff --git a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx
index 034bde5d2ca98821f371996a04783894a6424c7e..abad37e04532abcf0e057e8228bc31a3ce74c0a1 100644
--- a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx
@@ -355,6 +355,8 @@ export class ValueRemappings extends React.Component {
         const mappedString =
           mappedOrUndefined !== undefined
             ? mappedOrUndefined.toString()
+            : original === null
+            ? "null"
             : original.toString();
 
         return [original, mappedString];
diff --git a/frontend/test/__support__/cypress.js b/frontend/test/__support__/cypress.js
index 7c935189819035bcfdc6139eaa29a0d304954111..09e32597a9eb8f53ec7c8c24529f7616146bb58e 100644
--- a/frontend/test/__support__/cypress.js
+++ b/frontend/test/__support__/cypress.js
@@ -109,21 +109,25 @@ export function typeAndBlurUsingLabel(label, value) {
 
 Cypress.on("uncaught:exception", (err, runnable) => false);
 
-export function withSampleDataset(f) {
-  cy.request("GET", "/api/database/1/metadata").then(({ body }) => {
-    const SAMPLE_DATASET = {};
+export function withDatabase(databaseId, f) {
+  cy.request("GET", `/api/database/${databaseId}/metadata`).then(({ body }) => {
+    const database = {};
     for (const table of body.tables) {
       const fields = {};
       for (const field of table.fields) {
         fields[field.name] = field.id;
       }
-      SAMPLE_DATASET[table.name] = fields;
-      SAMPLE_DATASET[table.name + "_ID"] = table.id;
+      database[table.name] = fields;
+      database[table.name + "_ID"] = table.id;
     }
-    f(SAMPLE_DATASET);
+    f(database);
   });
 }
 
+export function withSampleDataset(f) {
+  return withDatabase(1, f);
+}
+
 export function visitAlias(alias) {
   cy.get(alias).then(url => {
     cy.visit(url);
diff --git a/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js
index a51f4681d98c3fc7ac65a0c190048a2bcf75e84f..b798e8b0a1c98751f89dcad3dc3a53510e93511f 100644
--- a/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js
@@ -2,7 +2,9 @@ import {
   signInAsAdmin,
   restore,
   withSampleDataset,
+  withDatabase,
   visitAlias,
+  popover,
 } from "__support__/cypress";
 
 describe("scenarios > admin > datamodel > field", () => {
@@ -159,5 +161,38 @@ describe("scenarios > admin > datamodel > field", () => {
       cy.contains("Custom mapping");
       cy.get('input[value="foo"]');
     });
+
+    it("allows 'Custom mapping' null values", () => {
+      restore("withSqlite");
+      signInAsAdmin();
+      const dbId = 2;
+      withDatabase(
+        dbId,
+        ({ number_with_nulls: { num }, number_with_nulls_ID }) =>
+          cy.visit(
+            `/admin/datamodel/database/${dbId}/table/${number_with_nulls_ID}/${num}/general`,
+          ),
+      );
+
+      // change to custom mapping
+      cy.findByText("Use original value").click();
+      popover()
+        .findByText("Custom mapping")
+        .click();
+
+      // update text for nulls from "null" to "nothin"
+      cy.get("input[value=null]")
+        .clear()
+        .type("nothin");
+      cy.findByText("Save").click();
+      cy.findByText("Saved!");
+
+      // check that it appears in QB
+      cy.visit("/question/new");
+      cy.findByText("Simple question").click();
+      cy.findByText("sqlite").click();
+      cy.findByText("Number With Nulls").click();
+      cy.findByText("nothin");
+    });
   });
 });
diff --git a/frontend/test/snapshot-creators/default.cy.snap.js b/frontend/test/snapshot-creators/default.cy.snap.js
index 9af881e279422f80f2642ad5429afb0bcf2c3e8e..61b643b4c781e1c3168e8942c22c81614638bb00 100644
--- a/frontend/test/snapshot-creators/default.cy.snap.js
+++ b/frontend/test/snapshot-creators/default.cy.snap.js
@@ -3,156 +3,198 @@ import {
   restore,
   USERS,
   withSampleDataset,
+  signInAsAdmin,
 } from "__support__/cypress";
 
-describe("default", () => {
-  it("default", () => {
-    snapshot("blank");
-    setup();
-    updateSettings();
-    addUsersAndGroups();
-    withSampleDataset(SAMPLE_DATASET => {
-      createQuestionAndDashboard(SAMPLE_DATASET);
+describe("snapshots", () => {
+  describe("default", () => {
+    it("default", () => {
+      snapshot("blank");
+      setup();
+      updateSettings();
+      addUsersAndGroups();
+      withSampleDataset(SAMPLE_DATASET => {
+        createQuestionAndDashboard(SAMPLE_DATASET);
+      });
+      snapshot("default");
+      restore("blank");
     });
-    snapshot("default");
-    restore("blank");
   });
-});
 
-function makeUserObject(name, groupIds) {
-  return {
-    first_name: USERS[name].first_name,
-    last_name: USERS[name].last_name,
-    email: USERS[name].username,
-    password: USERS[name].password,
-    group_ids: groupIds,
-  };
-}
-
-function setup() {
-  cy.request("GET", "/api/session/properties").then(({ body: properties }) => {
-    cy.request("POST", "/api/setup", {
-      token: properties["setup-token"],
-      user: makeUserObject("admin"),
-      prefs: {
-        site_name: "Epic Team",
-        allow_tracking: false,
+  function makeUserObject(name, groupIds) {
+    return {
+      first_name: USERS[name].first_name,
+      last_name: USERS[name].last_name,
+      email: USERS[name].username,
+      password: USERS[name].password,
+      group_ids: groupIds,
+    };
+  }
+
+  function setup() {
+    cy.request("GET", "/api/session/properties").then(
+      ({ body: properties }) => {
+        cy.request("POST", "/api/setup", {
+          token: properties["setup-token"],
+          user: makeUserObject("admin"),
+          prefs: {
+            site_name: "Epic Team",
+            allow_tracking: false,
+          },
+          database: null,
+        });
       },
-      database: null,
+    );
+  }
+
+  function updateSettings() {
+    cy.request("PUT", "/api/setting/enable-public-sharing", { value: true });
+    cy.request("PUT", "/api/setting/enable-embedding", { value: true });
+    cy.request("PUT", "/api/setting/embedding-secret-key", {
+      value: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
     });
-  });
-}
 
-function updateSettings() {
-  cy.request("PUT", "/api/setting/enable-public-sharing", { value: true });
-  cy.request("PUT", "/api/setting/enable-embedding", { value: true });
-  cy.request("PUT", "/api/setting/embedding-secret-key", {
-    value: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-  });
+    // update the Sample db connection string so it is valid in both CI and locally
+    cy.request("GET", "/api/database/1").then(response => {
+      response.body.details.db =
+        "./resources/sample-dataset.db;USER=GUEST;PASSWORD=guest";
+      cy.request("PUT", "/api/database/1", response.body);
+    });
+  }
 
-  // update the Sample db connection string so it is valid in both CI and locally
-  cy.request("GET", "/api/database/1").then(response => {
-    response.body.details.db =
-      "./resources/sample-dataset.db;USER=GUEST;PASSWORD=guest";
-    cy.request("PUT", "/api/database/1", response.body);
-  });
-}
-
-const ALL_USERS_GROUP = 1;
-const COLLECTION_GROUP = 4;
-const DATA_GROUP = 5;
-
-function addUsersAndGroups() {
-  // groups
-  cy.request("POST", "/api/permissions/group", { name: "collection" }); // 4
-  cy.request("POST", "/api/permissions/group", { name: "data" }); // 5
-
-  // additional users
-  cy.request(
-    "POST",
-    "/api/user",
-    makeUserObject("normal", [ALL_USERS_GROUP, COLLECTION_GROUP, DATA_GROUP]),
-  );
-  cy.request(
-    "POST",
-    "/api/user",
-    makeUserObject("nodata", [ALL_USERS_GROUP, COLLECTION_GROUP]),
-  );
-  cy.request(
-    "POST",
-    "/api/user",
-    makeUserObject("nocollection", [ALL_USERS_GROUP, DATA_GROUP]),
-  );
-  cy.request("POST", "/api/user", makeUserObject("none", [ALL_USERS_GROUP]));
-
-  // Make a call to `/api/user` because some things (personal collections) get created there
-  cy.request("GET", "/api/user");
-
-  // permissions
-  cy.request("PUT", "/api/permissions/graph", {
-    revision: 0,
-    groups: {
-      [ALL_USERS_GROUP]: { "1": { schemas: "none", native: "none" } },
-      [DATA_GROUP]: { "1": { schemas: "all", native: "write" } },
-      [COLLECTION_GROUP]: { "1": { schemas: "none", native: "none" } },
-    },
-  });
-  cy.request("PUT", "/api/collection/graph", {
-    revision: 0,
-    groups: {
-      [ALL_USERS_GROUP]: { root: "none" },
-      [DATA_GROUP]: { root: "none" },
-      [COLLECTION_GROUP]: { root: "write" },
-    },
-  });
-}
-
-function createQuestionAndDashboard({ ORDERS, ORDERS_ID }) {
-  // question 1: Orders
-  cy.request("POST", "/api/card", {
-    name: "Orders",
-    display: "table",
-    visualization_settings: {},
-    dataset_query: {
-      database: 1,
-      query: { "source-table": ORDERS_ID },
-      type: "query",
-    },
-  });
+  const ALL_USERS_GROUP = 1;
+  const COLLECTION_GROUP = 4;
+  const DATA_GROUP = 5;
 
-  // question 2: Orders, Count
-  cy.request("POST", "/api/card", {
-    name: "Orders, Count",
-    display: "table",
-    visualization_settings: {},
-    dataset_query: {
-      database: 1,
-      query: { "source-table": ORDERS_ID, aggregation: [["count"]] },
-      type: "query",
-    },
-  });
+  function addUsersAndGroups() {
+    // groups
+    cy.request("POST", "/api/permissions/group", { name: "collection" }); // 4
+    cy.request("POST", "/api/permissions/group", { name: "data" }); // 5
+
+    // additional users
+    cy.request(
+      "POST",
+      "/api/user",
+      makeUserObject("normal", [ALL_USERS_GROUP, COLLECTION_GROUP, DATA_GROUP]),
+    );
+    cy.request(
+      "POST",
+      "/api/user",
+      makeUserObject("nodata", [ALL_USERS_GROUP, COLLECTION_GROUP]),
+    );
+    cy.request(
+      "POST",
+      "/api/user",
+      makeUserObject("nocollection", [ALL_USERS_GROUP, DATA_GROUP]),
+    );
+    cy.request("POST", "/api/user", makeUserObject("none", [ALL_USERS_GROUP]));
 
-  cy.request("POST", "/api/card", {
-    name: "Orders, Count, Grouped by Created At (year)",
-    dataset_query: {
-      type: "query",
-      query: {
-        "source-table": ORDERS_ID,
-        aggregation: [["count"]],
-        breakout: [["datetime-field", ["field-id", ORDERS.CREATED_AT], "year"]],
+    // Make a call to `/api/user` because some things (personal collections) get created there
+    cy.request("GET", "/api/user");
+
+    // permissions
+    cy.request("PUT", "/api/permissions/graph", {
+      revision: 0,
+      groups: {
+        [ALL_USERS_GROUP]: { "1": { schemas: "none", native: "none" } },
+        [DATA_GROUP]: { "1": { schemas: "all", native: "write" } },
+        [COLLECTION_GROUP]: { "1": { schemas: "none", native: "none" } },
       },
-      database: 1,
-    },
-    display: "line",
-    visualization_settings: {},
-  });
+    });
+    cy.request("PUT", "/api/collection/graph", {
+      revision: 0,
+      groups: {
+        [ALL_USERS_GROUP]: { root: "none" },
+        [DATA_GROUP]: { root: "none" },
+        [COLLECTION_GROUP]: { root: "write" },
+      },
+    });
+  }
+
+  function createQuestionAndDashboard({ ORDERS, ORDERS_ID }) {
+    // question 1: Orders
+    cy.request("POST", "/api/card", {
+      name: "Orders",
+      display: "table",
+      visualization_settings: {},
+      dataset_query: {
+        database: 1,
+        query: { "source-table": ORDERS_ID },
+        type: "query",
+      },
+    });
 
-  // dashboard 1: Orders in a dashboard
-  cy.request("POST", "/api/dashboard", { name: "Orders in a dashboard" });
-  cy.request("POST", `/api/dashboard/1/cards`, { cardId: 1 });
+    // question 2: Orders, Count
+    cy.request("POST", "/api/card", {
+      name: "Orders, Count",
+      display: "table",
+      visualization_settings: {},
+      dataset_query: {
+        database: 1,
+        query: { "source-table": ORDERS_ID, aggregation: [["count"]] },
+        type: "query",
+      },
+    });
 
-  // dismiss the "it's ok to play around" modal
-  Object.values(USERS).map((_, index) =>
-    cy.request("PUT", `/api/user/${index + 1}/qbnewb`, {}),
-  );
-}
+    cy.request("POST", "/api/card", {
+      name: "Orders, Count, Grouped by Created At (year)",
+      dataset_query: {
+        type: "query",
+        query: {
+          "source-table": ORDERS_ID,
+          aggregation: [["count"]],
+          breakout: [
+            ["datetime-field", ["field-id", ORDERS.CREATED_AT], "year"],
+          ],
+        },
+        database: 1,
+      },
+      display: "line",
+      visualization_settings: {},
+    });
+
+    // dashboard 1: Orders in a dashboard
+    cy.request("POST", "/api/dashboard", { name: "Orders in a dashboard" });
+    cy.request("POST", `/api/dashboard/1/cards`, { cardId: 1 });
+
+    // dismiss the "it's ok to play around" modal
+    Object.values(USERS).map((_, index) =>
+      cy.request("PUT", `/api/user/${index + 1}/qbnewb`, {}),
+    );
+  }
+
+  // TODO: It'd be nice to have one file per snapshot.
+  // To do that we need to enforce execution order among them.
+  describe("withSqlite", () => {
+    it("withSqlite", () => {
+      restore("default");
+      signInAsAdmin();
+      cy.request("POST", "/api/database", {
+        engine: "sqlite",
+        name: "sqlite",
+        details: { db: "./resources/sqlite-fixture.db" },
+        auto_run_queries: true,
+        is_full_sync: true,
+        schedules: {
+          cache_field_values: {
+            schedule_day: null,
+            schedule_frame: null,
+            schedule_hour: 0,
+            schedule_type: "daily",
+          },
+          metadata_sync: {
+            schedule_day: null,
+            schedule_frame: null,
+            schedule_hour: null,
+            schedule_type: "hourly",
+          },
+        },
+      });
+      cy.request("POST", "/api/database/2/sync_schema");
+      cy.request("POST", "/api/database/2/rescan_values");
+      cy.wait(1000); // wait for sync
+      snapshot("withSqlite");
+      restore("blank");
+    });
+  });
+});
diff --git a/resources/sqlite-fixture.db b/resources/sqlite-fixture.db
new file mode 100644
index 0000000000000000000000000000000000000000..b390f7cc7d33396a245eb880ba662f8e802683e3
Binary files /dev/null and b/resources/sqlite-fixture.db differ