Make passwords optional for entries
- validateEntry no longer requires encryptedPassword; only title is required (schema.js). encryptedPassword documented as optional. - EntryForm: decrypt-guards empty password on edit; stores empty string (no encryption) when the password field is blank; label is 'Password' (no *). - EntryDetail: only renders the Password field when the entry has one; decrypt guards empty. - Tests: schema validation updated (password optional); 143 total pass.
This commit is contained in:
parent
a89c7811e1
commit
b8e7ce75f7
101
dist/index.html
vendored
101
dist/index.html
vendored
@ -5017,7 +5017,7 @@ function generateId() {
|
||||
* @property {string} id - Unique identifier
|
||||
* @property {string} title - Display name (e.g. "GitHub", "Gmail")
|
||||
* @property {string} [username] - Login username or email (optional)
|
||||
* @property {string} encryptedPassword - AES-GCM encrypted password blob (JSON string)
|
||||
* @property {string} [encryptedPassword] - AES-GCM encrypted password blob (JSON string); optional
|
||||
* @property {string} [url] - Website URL
|
||||
* @property {string} [notes] - Free-form notes
|
||||
* @property {string} [groupId] - Reference to a Group id (empty string = no group)
|
||||
@ -5031,7 +5031,7 @@ function generateId() {
|
||||
* @param {Object} data
|
||||
* @param {string} data.title
|
||||
* @param {string} [data.username]
|
||||
* @param {string} data.encryptedPassword - Must already be encrypted
|
||||
* @param {string} [data.encryptedPassword] - Must already be encrypted (optional; empty string = no password)
|
||||
* @param {string} [data.url]
|
||||
* @param {string} [data.notes]
|
||||
* @param {string} [data.groupId]
|
||||
@ -5116,7 +5116,6 @@ function createGroup(name, color) {
|
||||
function validateEntry(data) {
|
||||
const errors = [];
|
||||
if (!data.title || !data.title.trim()) errors.push("Title is required");
|
||||
if (!data.encryptedPassword) errors.push("Password is required");
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
@ -6542,11 +6541,12 @@ var root_4$3 = /* @__PURE__ */ from_html(`<div class="empty-state svelte-dssgjx"
|
||||
var root_6$2 = /* @__PURE__ */ from_html(`<button class="btn btn-primary btn-sm">↩️ Restore</button> <button class="btn btn-danger btn-sm">🗑 Delete Forever</button>`, 1);
|
||||
var root_7$2 = /* @__PURE__ */ from_html(`<button class="btn btn-ghost btn-sm">✏️ Edit</button> <button class="btn btn-danger btn-sm">🗑 Move to Trash</button>`, 1);
|
||||
var root_8$2 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Username</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy username">📋</button></div></div>`);
|
||||
var root_9$1 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">URL</span> <div class="field-value svelte-dssgjx"><a target="_blank" rel="noopener noreferrer" class="svelte-dssgjx"> </a> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy URL">📋</button></div></div>`);
|
||||
var root_10 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Notes</span> <div class="field-value notes svelte-dssgjx"> </div></div>`);
|
||||
var root_11 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Move to trash confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Move to Trash</h3> <p class="svelte-dssgjx">Move "<strong> </strong>" to the trash? You can restore it later.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root_12 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Permanent delete confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Delete Forever</h3> <p class="svelte-dssgjx">Permanently delete "<strong> </strong>"? This cannot be undone.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root_5$3 = /* @__PURE__ */ from_html(`<div class="detail-card svelte-dssgjx"><div class="detail-header svelte-dssgjx"><h2 class="svelte-dssgjx"> </h2> <div class="header-actions svelte-dssgjx"><!></div></div> <div class="detail-fields svelte-dssgjx"><!> <div class="detail-field"><span class="field-label svelte-dssgjx">Password</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy password">📋</button></div></div> <!> <!></div> <div class="detail-meta svelte-dssgjx"><span class="text-xs text-muted"> </span> <span class="text-xs text-muted"> </span></div></div> <!> <!>`, 1);
|
||||
var root_9$1 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Password</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy password">📋</button></div></div>`);
|
||||
var root_10 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">URL</span> <div class="field-value svelte-dssgjx"><a target="_blank" rel="noopener noreferrer" class="svelte-dssgjx"> </a> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy URL">📋</button></div></div>`);
|
||||
var root_11 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Notes</span> <div class="field-value notes svelte-dssgjx"> </div></div>`);
|
||||
var root_12 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Move to trash confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Move to Trash</h3> <p class="svelte-dssgjx">Move "<strong> </strong>" to the trash? You can restore it later.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root_13$1 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Permanent delete confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Delete Forever</h3> <p class="svelte-dssgjx">Permanently delete "<strong> </strong>"? This cannot be undone.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root_5$3 = /* @__PURE__ */ from_html(`<div class="detail-card svelte-dssgjx"><div class="detail-header svelte-dssgjx"><h2 class="svelte-dssgjx"> </h2> <div class="header-actions svelte-dssgjx"><!></div></div> <div class="detail-fields svelte-dssgjx"><!> <!> <!> <!></div> <div class="detail-meta svelte-dssgjx"><span class="text-xs text-muted"> </span> <span class="text-xs text-muted"> </span></div></div> <!> <!>`, 1);
|
||||
var root$4 = /* @__PURE__ */ from_html(`<div class="entry-detail"><!> <!></div>`);
|
||||
function EntryDetail($$anchor, $$props) {
|
||||
push($$props, true);
|
||||
@ -6566,7 +6566,7 @@ function EntryDetail($$anchor, $$props) {
|
||||
set(error, "");
|
||||
try {
|
||||
set(entry, await getEntryById($$props.entryId), true);
|
||||
if (get(entry) && app$1.encryptionKey) set(decryptedPassword, await decrypt(get(entry).encryptedPassword, app$1.encryptionKey), true);
|
||||
if (get(entry) && app$1.encryptionKey) set(decryptedPassword, get(entry).encryptedPassword ? await decrypt(get(entry).encryptedPassword, app$1.encryptionKey) : "", true);
|
||||
} catch (e) {
|
||||
set(error, "Failed to load entry: " + e.message);
|
||||
}
|
||||
@ -6698,20 +6698,33 @@ function EntryDetail($$anchor, $$props) {
|
||||
if_block(node_3, ($$render) => {
|
||||
if (get(entry).username) $$render(consequent_5);
|
||||
});
|
||||
var div_11 = sibling(node_3, 2);
|
||||
var div_12 = sibling(child(div_11), 2);
|
||||
var span_1 = child(div_12);
|
||||
var text_5 = child(span_1, true);
|
||||
reset(span_1);
|
||||
var button_5 = sibling(span_1, 2);
|
||||
var text_6 = child(button_5, true);
|
||||
reset(button_5);
|
||||
var button_6 = sibling(button_5, 2);
|
||||
reset(div_12);
|
||||
reset(div_11);
|
||||
var node_4 = sibling(div_11, 2);
|
||||
var node_4 = sibling(node_3, 2);
|
||||
var consequent_6 = ($$anchor) => {
|
||||
var div_13 = root_9$1();
|
||||
var div_11 = root_9$1();
|
||||
var div_12 = sibling(child(div_11), 2);
|
||||
var span_1 = child(div_12);
|
||||
var text_5 = child(span_1, true);
|
||||
reset(span_1);
|
||||
var button_5 = sibling(span_1, 2);
|
||||
var text_6 = child(button_5, true);
|
||||
reset(button_5);
|
||||
var button_6 = sibling(button_5, 2);
|
||||
reset(div_12);
|
||||
reset(div_11);
|
||||
template_effect(() => {
|
||||
set_text(text_5, get(passwordVisible) ? get(decryptedPassword) : "••••••••••••");
|
||||
set_text(text_6, get(passwordVisible) ? "🙈" : "👁");
|
||||
});
|
||||
delegated("click", button_5, () => set(passwordVisible, !get(passwordVisible)));
|
||||
delegated("click", button_6, () => copyToClipboard(get(decryptedPassword), "Password"));
|
||||
append($$anchor, div_11);
|
||||
};
|
||||
if_block(node_4, ($$render) => {
|
||||
if (get(entry).encryptedPassword) $$render(consequent_6);
|
||||
});
|
||||
var node_5 = sibling(node_4, 2);
|
||||
var consequent_7 = ($$anchor) => {
|
||||
var div_13 = root_10();
|
||||
var div_14 = sibling(child(div_13), 2);
|
||||
var a = child(div_14);
|
||||
var text_7 = child(a, true);
|
||||
@ -6726,12 +6739,12 @@ function EntryDetail($$anchor, $$props) {
|
||||
delegated("click", button_7, () => copyToClipboard(get(entry).url, "URL"));
|
||||
append($$anchor, div_13);
|
||||
};
|
||||
if_block(node_4, ($$render) => {
|
||||
if (get(entry).url) $$render(consequent_6);
|
||||
if_block(node_5, ($$render) => {
|
||||
if (get(entry).url) $$render(consequent_7);
|
||||
});
|
||||
var node_5 = sibling(node_4, 2);
|
||||
var consequent_7 = ($$anchor) => {
|
||||
var div_15 = root_10();
|
||||
var node_6 = sibling(node_5, 2);
|
||||
var consequent_8 = ($$anchor) => {
|
||||
var div_15 = root_11();
|
||||
var div_16 = sibling(child(div_15), 2);
|
||||
var text_8 = child(div_16, true);
|
||||
reset(div_16);
|
||||
@ -6739,8 +6752,8 @@ function EntryDetail($$anchor, $$props) {
|
||||
template_effect(() => set_text(text_8, get(entry).notes));
|
||||
append($$anchor, div_15);
|
||||
};
|
||||
if_block(node_5, ($$render) => {
|
||||
if (get(entry).notes) $$render(consequent_7);
|
||||
if_block(node_6, ($$render) => {
|
||||
if (get(entry).notes) $$render(consequent_8);
|
||||
});
|
||||
reset(div_8);
|
||||
var div_17 = sibling(div_8, 2);
|
||||
@ -6752,9 +6765,9 @@ function EntryDetail($$anchor, $$props) {
|
||||
reset(span_3);
|
||||
reset(div_17);
|
||||
reset(div_5);
|
||||
var node_6 = sibling(div_5, 2);
|
||||
var consequent_8 = ($$anchor) => {
|
||||
var div_18 = root_11();
|
||||
var node_7 = sibling(div_5, 2);
|
||||
var consequent_9 = ($$anchor) => {
|
||||
var div_18 = root_12();
|
||||
var div_19 = child(div_18);
|
||||
var p = sibling(child(div_19), 2);
|
||||
var strong = sibling(child(p));
|
||||
@ -6781,12 +6794,12 @@ function EntryDetail($$anchor, $$props) {
|
||||
delegated("click", button_9, () => set(showDeleteConfirm, false));
|
||||
append($$anchor, div_18);
|
||||
};
|
||||
if_block(node_6, ($$render) => {
|
||||
if (get(showDeleteConfirm)) $$render(consequent_8);
|
||||
if_block(node_7, ($$render) => {
|
||||
if (get(showDeleteConfirm)) $$render(consequent_9);
|
||||
});
|
||||
var node_7 = sibling(node_6, 2);
|
||||
var consequent_9 = ($$anchor) => {
|
||||
var div_21 = root_12();
|
||||
var node_8 = sibling(node_7, 2);
|
||||
var consequent_10 = ($$anchor) => {
|
||||
var div_21 = root_13$1();
|
||||
var div_22 = child(div_21);
|
||||
var p_1 = sibling(child(div_22), 2);
|
||||
var strong_1 = sibling(child(p_1));
|
||||
@ -6813,18 +6826,14 @@ function EntryDetail($$anchor, $$props) {
|
||||
delegated("click", button_11, () => set(showPermanentDeleteConfirm, false));
|
||||
append($$anchor, div_21);
|
||||
};
|
||||
if_block(node_7, ($$render) => {
|
||||
if (get(showPermanentDeleteConfirm)) $$render(consequent_9);
|
||||
if_block(node_8, ($$render) => {
|
||||
if (get(showPermanentDeleteConfirm)) $$render(consequent_10);
|
||||
});
|
||||
template_effect(($0, $1) => {
|
||||
set_text(text_3, get(entry).title);
|
||||
set_text(text_5, get(passwordVisible) ? get(decryptedPassword) : "••••••••••••");
|
||||
set_text(text_6, get(passwordVisible) ? "🙈" : "👁");
|
||||
set_text(text_9, `Created: ${$0 ?? ""}`);
|
||||
set_text(text_10, `Updated: ${$1 ?? ""}`);
|
||||
}, [() => new Date(get(entry).createdAt).toLocaleString(), () => new Date(get(entry).updatedAt).toLocaleString()]);
|
||||
delegated("click", button_5, () => set(passwordVisible, !get(passwordVisible)));
|
||||
delegated("click", button_6, () => copyToClipboard(get(decryptedPassword), "Password"));
|
||||
append($$anchor, fragment);
|
||||
};
|
||||
if_block(node_1, ($$render) => {
|
||||
@ -6845,7 +6854,7 @@ var root_3$2 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-pafazm
|
||||
var root_5$2 = /* @__PURE__ */ from_html(`<div class="validation-error svelte-pafazm"> </div>`);
|
||||
var root_4$2 = /* @__PURE__ */ from_html(`<div class="validation-errors svelte-pafazm"></div>`);
|
||||
var root_7$1 = /* @__PURE__ */ from_html(`<option> </option>`);
|
||||
var root_2$2 = /* @__PURE__ */ from_html(`<!> <form class="form-card svelte-pafazm"><!> <div class="form-group"><label for="title">Title *</label> <input id="title" type="text" placeholder="e.g. GitHub, Gmail"/></div> <div class="form-group"><label for="username">Username / Email</label> <input id="username" type="text" placeholder="username or email"/></div> <div class="form-group"><label for="password">Password *</label> <div class="password-input-group svelte-pafazm"><input id="password" placeholder="Password" class="svelte-pafazm"/> <button type="button" class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button type="button" class="btn btn-ghost btn-sm" title="Generate password">🎲</button></div></div> <div class="form-group"><label for="url">URL</label> <input id="url" type="url" placeholder="https://example.com"/></div> <div class="form-group"><label for="group">Group</label> <select id="group"><option>No group</option><!></select></div> <div class="form-group"><label for="notes">Notes</label> <textarea id="notes" placeholder="Any additional notes..."></textarea></div> <div class="form-actions svelte-pafazm"><button type="submit" class="btn btn-primary"> </button> <button type="button" class="btn btn-ghost">Cancel</button></div></form>`, 1);
|
||||
var root_2$2 = /* @__PURE__ */ from_html(`<!> <form class="form-card svelte-pafazm"><!> <div class="form-group"><label for="title">Title *</label> <input id="title" type="text" placeholder="e.g. GitHub, Gmail"/></div> <div class="form-group"><label for="username">Username / Email</label> <input id="username" type="text" placeholder="username or email"/></div> <div class="form-group"><label for="password">Password</label> <div class="password-input-group svelte-pafazm"><input id="password" placeholder="Password" class="svelte-pafazm"/> <button type="button" class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button type="button" class="btn btn-ghost btn-sm" title="Generate password">🎲</button></div></div> <div class="form-group"><label for="url">URL</label> <input id="url" type="url" placeholder="https://example.com"/></div> <div class="form-group"><label for="group">Group</label> <select id="group"><option>No group</option><!></select></div> <div class="form-group"><label for="notes">Notes</label> <textarea id="notes" placeholder="Any additional notes..."></textarea></div> <div class="form-actions svelte-pafazm"><button type="submit" class="btn btn-primary"> </button> <button type="button" class="btn btn-ghost">Cancel</button></div></form>`, 1);
|
||||
var root$3 = /* @__PURE__ */ from_html(`<div class="entry-form"><!></div>`);
|
||||
function EntryForm($$anchor, $$props) {
|
||||
push($$props, true);
|
||||
@ -6872,7 +6881,7 @@ function EntryForm($$anchor, $$props) {
|
||||
if (entry) {
|
||||
set(title, entry.title, true);
|
||||
set(username, entry.username, true);
|
||||
set(password, await decrypt(entry.encryptedPassword, app$1.encryptionKey), true);
|
||||
set(password, entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app$1.encryptionKey) : "", true);
|
||||
set(url, entry.url || "", true);
|
||||
set(notes, entry.notes || "", true);
|
||||
set(groupId, entry.groupId || "", true);
|
||||
@ -6902,7 +6911,7 @@ function EntryForm($$anchor, $$props) {
|
||||
set(saving, false);
|
||||
return;
|
||||
}
|
||||
const encryptedPassword = await encrypt(get(password), app$1.encryptionKey);
|
||||
const encryptedPassword = get(password) ? await encrypt(get(password), app$1.encryptionKey) : "";
|
||||
if (get(isEdit)) await updateEntry(updateEntry$1(await getEntryById($$props.entryId), {
|
||||
title: get(title),
|
||||
username: get(username),
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
try {
|
||||
entry = await getEntryById(entryId)
|
||||
if (entry && app.encryptionKey) {
|
||||
decryptedPassword = await decrypt(entry.encryptedPassword, app.encryptionKey)
|
||||
decryptedPassword = entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app.encryptionKey) : ''
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Failed to load entry: ' + e.message
|
||||
@ -130,6 +130,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if entry.encryptedPassword}
|
||||
<div class="detail-field">
|
||||
<span class="field-label">Password</span>
|
||||
<div class="field-value">
|
||||
@ -140,6 +141,7 @@
|
||||
<button class="btn btn-ghost btn-sm copy-btn" onclick={() => copyToClipboard(decryptedPassword, 'Password')} title="Copy password">📋</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if entry.url}
|
||||
<div class="detail-field">
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
if (entry) {
|
||||
title = entry.title
|
||||
username = entry.username
|
||||
password = await decrypt(entry.encryptedPassword, app.encryptionKey)
|
||||
password = entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app.encryptionKey) : ''
|
||||
url = entry.url || ''
|
||||
notes = entry.notes || ''
|
||||
groupId = entry.groupId || ''
|
||||
@ -66,7 +66,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const encryptedPassword = await encrypt(password, app.encryptionKey)
|
||||
const encryptedPassword = password ? await encrypt(password, app.encryptionKey) : ''
|
||||
|
||||
if (isEdit) {
|
||||
const existing = await getEntryById(entryId)
|
||||
@ -128,7 +128,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password *</label>
|
||||
<label for="password">Password</label>
|
||||
<div class="password-input-group">
|
||||
<input
|
||||
id="password"
|
||||
|
||||
@ -30,7 +30,7 @@ export function generateId() {
|
||||
* @property {string} id - Unique identifier
|
||||
* @property {string} title - Display name (e.g. "GitHub", "Gmail")
|
||||
* @property {string} [username] - Login username or email (optional)
|
||||
* @property {string} encryptedPassword - AES-GCM encrypted password blob (JSON string)
|
||||
* @property {string} [encryptedPassword] - AES-GCM encrypted password blob (JSON string); optional
|
||||
* @property {string} [url] - Website URL
|
||||
* @property {string} [notes] - Free-form notes
|
||||
* @property {string} [groupId] - Reference to a Group id (empty string = no group)
|
||||
@ -45,7 +45,7 @@ export function generateId() {
|
||||
* @param {Object} data
|
||||
* @param {string} data.title
|
||||
* @param {string} [data.username]
|
||||
* @param {string} data.encryptedPassword - Must already be encrypted
|
||||
* @param {string} [data.encryptedPassword] - Must already be encrypted (optional; empty string = no password)
|
||||
* @param {string} [data.url]
|
||||
* @param {string} [data.notes]
|
||||
* @param {string} [data.groupId]
|
||||
@ -127,8 +127,7 @@ export function createGroup(name, color) {
|
||||
export function validateEntry(data) {
|
||||
const errors = []
|
||||
if (!data.title || !data.title.trim()) errors.push('Title is required')
|
||||
|
||||
if (!data.encryptedPassword) errors.push('Password is required')
|
||||
// Password is optional — an entry may store no password at all.
|
||||
return { valid: errors.length === 0, errors }
|
||||
}
|
||||
|
||||
|
||||
@ -176,16 +176,22 @@ describe('validateEntry', () => {
|
||||
expect(result.errors).toContain('Title is required')
|
||||
})
|
||||
|
||||
it('should fail with missing encryptedPassword', () => {
|
||||
it('should pass with no encryptedPassword (password optional)', () => {
|
||||
const result = validateEntry({ title: 'GitHub' })
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('Password is required')
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should report multiple errors', () => {
|
||||
it('should pass with an empty password string', () => {
|
||||
const result = validateEntry({ title: 'GitHub', encryptedPassword: '' })
|
||||
expect(result.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('should report only the title error when password is missing (not required)', () => {
|
||||
const result = validateEntry({ title: '' })
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors.length).toBe(2)
|
||||
expect(result.errors.length).toBe(1)
|
||||
expect(result.errors).toContain('Title is required')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user