Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/assets/javascripts/tiptap.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/assets/stylesheets/index.css

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions app/templates/views/storybook/sms-character-info-with-prefix.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% from "components/sms-character-info.html" import sms_character_info %}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a separate storybook page for when the prefix is on.


<h2 class="heading-large">SMS Character Info - With Service Prefix</h2>
<p>Fragment count when a service prefix is prepended to the message. The 7-character "NotifyBC" prefix + ": " separator (9 chars total overhead) is counted toward the SMS fragment limit.</p>
<p>Try typing 152 characters in the box below to see it cross into 2 fragments (161 total with prefix overhead).</p>
<hr />

<div class="md:w-2/3">

<h2 class="heading-medium">Story: SMS character info panel with prefix</h2>

<label class="form-label" for="template_content">Message content</label>
<textarea
id="template_content"
name="template_content"
class="form-control w-full"
data-testid="template-content"
rows="5"
></textarea>

{{ sms_character_info(sms_daily_limit="1,000", sms_prefix="NotifyBC") }}

</div>
4 changes: 4 additions & 0 deletions app/templates/views/storybook/sms-character-info.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ <h2 class="heading-medium">Story: SMS character info panel</h2>
{{ sms_character_info(sms_daily_limit="1,000", sms_prefix="") }}

</div>
<p class="mt-8">
<a href="{{ url_for('main.storybook', component='sms-character-info-with-prefix') }}"
class="text-blue-500 hover:underline">View with service prefix example →</a>
</p>
6 changes: 5 additions & 1 deletion app/templates/views/storybook/storybook-menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@
{

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the existing page to the menu so we can navigate to it.

'name': 'Scheduler',
'path': 'scheduler'
}
},
{
'name': 'SMS Character Info',
'path': 'sms-character-info'
},
] %}
<ul class="list list-bullet ml-10">
<!-- loop through components -->
Expand Down
269 changes: 0 additions & 269 deletions tests/javascripts/smsCharacterInfo.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const helpers = require("./support/helpers.js");

// Locate the CSV via the installed notifications_utils Python package
const notificationsUtilsDir = execSync(
Expand All @@ -25,90 +24,6 @@ const smsFragmentTestCases = fs
return { sms_content, expected_fragments };
});

// Minimal DOM structure mirroring the sms-character-info macro
function buildDOM({ prefix = "", initialContent = "" } = {}) {
document.body.innerHTML = `
<div
id="sms-character-info"
data-sms-prefix="${prefix}"
data-testid="sms-character-info"
>
<p id="sms-fragment-count">
<strong id="sms-fragment-count-text"></strong>
<span id="sms-fragment-count-suffix"></span>
</p>
<p id="sms-daily-limit">
<strong id="sms-daily-limit-value">1,000</strong>
is your daily text message limit
</p>
</div>
<textarea id="template_content">${initialContent}</textarea>`;
}

beforeAll(() => {
jest.useFakeTimers();

// Mirror the APP_PHRASES values set by Flask in main_template.html
window.APP_PHRASES = {
sms_estimate: "Estimate: {} text messages.",
sms_estimate_one: "Estimate: 1 text message.",
sms_variables_warning: "Variables may increase number of messages.",
sms_count: "{} text messages.",
sms_one: "1 text message.",
};
});

afterAll(() => {
require("./support/teardown.js");
});

afterEach(() => {
document.body.innerHTML = "";
// Reset the module so the IIFE re-executes with fresh DOM on the next require
jest.resetModules();
});

// Helper: set textarea value and fire input event, then flush the debounce
function typeIntoTextarea(textarea, value) {
textarea.value = value;
helpers.triggerEvent(textarea, "input");
jest.advanceTimersByTime(150);
}

// ── Guard conditions ─────────────────────────────────────────────────────────

describe("Guard conditions", () => {
test("does not throw when #sms-character-info container is absent", () => {
document.body.innerHTML = `<textarea id="template_content"></textarea>`;
expect(() =>
require("../../app/assets/javascripts/smsCharacterInfo.js")
).not.toThrow();
});

test("does not throw when #template_content textarea is absent", () => {
document.body.innerHTML = `<div id="sms-character-info" data-sms-prefix=""></div>`;
expect(() =>
require("../../app/assets/javascripts/smsCharacterInfo.js")
).not.toThrow();
});
});

// ── Initial render (page load) ───────────────────────────────────────────────

describe("Initial render on page load", () => {
test("shows '1 text message.' for an empty textarea", () => {
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");

expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("1 text message.");
expect(
document.getElementById("sms-fragment-count-suffix").textContent
).toBe("");
});
});

// ── Fragment count (shared CSV test cases) ───────────────────────────────────

describe("Fragment count (shared CSV test cases)", () => {
Expand All @@ -132,187 +47,3 @@ describe("Fragment count (shared CSV test cases)", () => {
});
});

// ── Service prefix counted in character total ────────────────────────────────

describe("Service prefix included in character count", () => {
let textarea;

// Prefix "TestSvc" = 7 chars, plus ": " = 9 chars overhead
beforeEach(() => {
buildDOM({ prefix: "TestSvc" });
require("../../app/assets/javascripts/smsCharacterInfo.js");
textarea = document.getElementById("template_content");
});

test("prefix overhead pushes 152-char message into 2 fragments", () => {
// 7 (prefix) + 2 (': ') + 152 (content) = 161 units → 2 fragments
typeIntoTextarea(textarea, "a".repeat(152));
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("2 text messages.");
});

test("same 152-char content without prefix stays at 1 fragment", () => {
// Rebuild without prefix
document.body.innerHTML = "";
jest.resetModules();
buildDOM({ prefix: "" });
require("../../app/assets/javascripts/smsCharacterInfo.js");
const ta = document.getElementById("template_content");

typeIntoTextarea(ta, "a".repeat(152));
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("1 text message.");
});
});

// ── Personalisation variables ────────────────────────────────────────────────

describe("Personalisation variables", () => {
let textarea;

beforeEach(() => {
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");
textarea = document.getElementById("template_content");
});

test("plain text shows count without 'Estimate:' prefix", () => {
typeIntoTextarea(textarea, "Hello world");
const text =
document.getElementById("sms-fragment-count-text").textContent;
expect(text).not.toContain("Estimate:");
expect(text).toBe("1 text message.");
});

test("text with ((variable)) shows 'Estimate:' prefix", () => {
typeIntoTextarea(textarea, "Hello ((name))");
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("Estimate: 1 text message.");
});

test("text with ((variable)) shows variables warning in suffix", () => {
typeIntoTextarea(textarea, "Hello ((name))");
expect(
document.getElementById("sms-fragment-count-suffix").textContent
).toBe(" Variables may increase number of messages.");
});

test("plain text has empty suffix", () => {
typeIntoTextarea(textarea, "Hello world");
expect(
document.getElementById("sms-fragment-count-suffix").textContent
).toBe("");
});

test("placeholder is stripped before counting characters", () => {
// 'Hello ' (6 chars) + ((name)) stripped → 6 chars counted → 1 fragment
typeIntoTextarea(textarea, "Hello ((name))");
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("Estimate: 1 text message.");
});

test("fallback placeholder syntax ((variable??fallback)) is also stripped", () => {
typeIntoTextarea(textarea, "Hi ((name??there)), your ref is ((ref??unknown))");
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("Estimate: 1 text message.");
expect(
document.getElementById("sms-fragment-count-suffix").textContent
).toBe(" Variables may increase number of messages.");
});
});

// ── Fragment count updates live on input ─────────────────────────────────────

describe("Live updates on input", () => {
let textarea;
let fragmentCountText;

beforeEach(() => {
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");
textarea = document.getElementById("template_content");
fragmentCountText = document.getElementById("sms-fragment-count-text");
});

test("updates fragment count as content changes", () => {
expect(fragmentCountText.textContent).toBe("1 text message.");

typeIntoTextarea(textarea, "a".repeat(161));
expect(fragmentCountText.textContent).toBe("2 text messages.");

typeIntoTextarea(textarea, "a".repeat(307));
expect(fragmentCountText.textContent).toBe("3 text messages.");

typeIntoTextarea(textarea, "a".repeat(10));
expect(fragmentCountText.textContent).toBe("1 text message.");
});

test("count does not change before debounce delay elapses", () => {
textarea.value = "a".repeat(161);
helpers.triggerEvent(textarea, "input");
// Do NOT advance timers — debounce hasn't fired yet
expect(fragmentCountText.textContent).toBe("1 text message."); // still initial
});

test("count updates after debounce delay elapses", () => {
textarea.value = "a".repeat(161);
helpers.triggerEvent(textarea, "input");
jest.advanceTimersByTime(150);
expect(fragmentCountText.textContent).toBe("2 text messages.");
});
});

// ── i18n via APP_PHRASES ─────────────────────────────────────────────────────

describe("i18n via APP_PHRASES", () => {
afterEach(() => {
// Restore defaults after each test in this group
window.APP_PHRASES = {
sms_estimate: "Estimate: {} text messages.",
sms_estimate_one: "Estimate: 1 text message.",
sms_variables_warning: "Variables may increase number of messages.",
sms_count: "{} text messages.",
sms_one: "1 text message.",
};
});

test("uses translated string from APP_PHRASES for single fragment", () => {
window.APP_PHRASES.sms_one = "1 message texte.";
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");

expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("1 message texte.");
});

test("uses translated string from APP_PHRASES for multiple fragments", () => {
window.APP_PHRASES.sms_count = "{} messages texte.";
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");
const textarea = document.getElementById("template_content");
typeIntoTextarea(textarea, "a".repeat(161));

expect(
document.getElementById("sms-fragment-count-text").textContent
).toBe("2 messages texte.");
});

test("falls back to default string when APP_PHRASES is not set", () => {
delete window.APP_PHRASES;
buildDOM();
require("../../app/assets/javascripts/smsCharacterInfo.js");

// Should not throw and should display the fallback text
expect(
document.getElementById("sms-fragment-count-text").textContent
).toBeTruthy();

window.APP_PHRASES = {}; // restore to empty for cleanup
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let Actions = {

let SmsCharacterInfo = {
URL: '/_storybook?component=sms-character-info',
URL_WITH_PREFIX: '/_storybook?component=sms-character-info-with-prefix',
Components,
...Actions,
};
Expand Down
Loading
Loading