<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: LS</title>
    <description>The latest articles on Forem by LS (@devangular).</description>
    <link>https://forem.com/devangular</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F628412%2Fbe409702-9795-41f7-994b-39f54834b271.png</url>
      <title>Forem: LS</title>
      <link>https://forem.com/devangular</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/devangular"/>
    <language>en</language>
    <item>
      <title>sql python note</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Thu, 04 Dec 2025 14:32:27 +0000</pubDate>
      <link>https://forem.com/devangular/sql-python-note-h14</link>
      <guid>https://forem.com/devangular/sql-python-note-h14</guid>
      <description></description>
    </item>
    <item>
      <title>flyway implementation</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Wed, 26 Nov 2025 17:23:28 +0000</pubDate>
      <link>https://forem.com/devangular/flyway-implementation-13bp</link>
      <guid>https://forem.com/devangular/flyway-implementation-13bp</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env python3
"""
Generate DML SQL for populating the etl_config table from a YAML file,
and write it as a Flyway migration.

Usage:
    python etl_config.py etl_config --name &amp;lt;NAME&amp;gt; [--yaml-file PATH] [--table etl_config]

Assumptions:
    - YAML structure (recommended):

        etl_config:
          - pipeline_name: accounts_daily
            enabled: true
            schedule_cron: "0 3 * * *"
            source_table: accounts_raw
            target_table: accounts_curated
          - ...

    - The script is stored somewhere under the repo root; update CONFIG_DIR and
      MIGRATIONS_DIR below to match your layout.
"""

import argparse
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Sequence

import yaml  # pip install pyyaml


# ---------------------------------------------------------------------------
# CONFIGURE THESE PATHS FOR YOUR REPO LAYOUT
# ---------------------------------------------------------------------------

# Repo root: adjust the number of `.parents[...]` if needed for your layout.
REPO_ROOT = Path(__file__).resolve().parents[2]

# Where YAML config files live (one per --name).
# e.g. infrastructure/flyway/data-domains/accounts_datastore/etl_config/config/&amp;lt;name&amp;gt;.yaml
CONFIG_DIR = (
    REPO_ROOT
    / "infrastructure"
    / "flyway"
    / "data-domains"
    / "accounts_datastore"
    / "etl_config"
    / "config"
)

# Where Flyway looks for migrations for this domain.
# e.g. infrastructure/flyway/data-domains/accounts_datastore/etl_config/migrations/
MIGRATIONS_DIR = (
    REPO_ROOT
    / "infrastructure"
    / "flyway"
    / "data-domains"
    / "accounts_datastore"
    / "etl_config"
    / "migrations"
)


# ---------------------------------------------------------------------------
# HELPERS
# ---------------------------------------------------------------------------

def sql_literal(value: Any) -&amp;gt; str:
    """
    Convert a Python value into a SQL literal string.

    - None -&amp;gt; NULL
    - bool -&amp;gt; true/false
    - numbers -&amp;gt; as-is
    - everything else -&amp;gt; single-quoted string with quotes escaped
    """
    if value is None:
        return "NULL"

    if isinstance(value, bool):
        return "true" if value else "false"

    if isinstance(value, (int, float)):
        return str(value)

    # treat everything else as string
    text = str(value)
    # escape single quotes by doubling them
    text = text.replace("'", "''")
    return f"'{text}'"


def load_yaml(path: Path) -&amp;gt; Dict[str, Any]:
    if not path.exists():
        raise FileNotFoundError(f"YAML config not found: {path}")
    with path.open("r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    if data is None:
        raise ValueError(f"YAML file is empty: {path}")
    return data


def extract_records(yaml_data: Dict[str, Any]) -&amp;gt; List[Dict[str, Any]]:
    """
    Try to get the list of records from YAML.

    Preferred: top-level key "etl_config" is a list.
    Fallback: YAML itself is a list.
    """
    if isinstance(yaml_data, list):
        if not yaml_data:
            raise ValueError("YAML list is empty; nothing to generate.")
        return yaml_data

    if "etl_config" in yaml_data:
        records = yaml_data["etl_config"]
        if not isinstance(records, list):
            raise ValueError("Expected 'etl_config' key to contain a list of records.")
        if not records:
            raise ValueError("'etl_config' list is empty; nothing to generate.")
        return records

    raise ValueError("YAML must be a list or contain an 'etl_config' list.")


def infer_columns(records: Sequence[Dict[str, Any]]) -&amp;gt; List[str]:
    """
    Infer the list of columns from the first record and ensure all records
    have exactly the same keys. This keeps the SQL deterministic.
    """
    first_keys = set(records[0].keys())
    if not first_keys:
        raise ValueError("First record has no columns; cannot infer schema.")

    for idx, rec in enumerate(records[1:], start=2):
        keys = set(rec.keys())
        if keys != first_keys:
            raise ValueError(
                f"Record #{idx} has different keys.\n"
                f"Expected: {sorted(first_keys)}\n"
                f"Found   : {sorted(keys)}"
            )

    return sorted(first_keys)


def build_insert_sql(table: str, records: Sequence[Dict[str, Any]]) -&amp;gt; str:
    """
    Build a SQL script with INSERTs for all records.
    """
    columns = infer_columns(records)
    col_list = ", ".join(columns)

    statements: List[str] = []

    for rec in records:
        values_sql = ", ".join(sql_literal(rec.get(col)) for col in columns)
        stmt = f"INSERT INTO {table} ({col_list}) VALUES ({values_sql});"
        statements.append(stmt)

    script = "BEGIN;\n\n" + "\n\n".join(statements) + "\n\nCOMMIT;\n"
    return script


def next_migration_filename(config_type: str, name: str) -&amp;gt; str:
    """
    Produce a Flyway-style migration filename, e.g.:

        V20251127_123456__etl_config_accounts_daily.sql
    """
    ts = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
    safe_type = config_type.replace("-", "_")
    safe_name = name.replace("-", "_")
    return f"V{ts}__{safe_type}_{safe_name}.sql"


def write_migration(content: str, filename: str) -&amp;gt; Path:
    MIGRATIONS_DIR.mkdir(parents=True, exist_ok=True)
    out_path = MIGRATIONS_DIR / filename
    out_path.write_text(content, encoding="utf-8")
    return out_path


# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------

def parse_args(argv: Sequence[str]) -&amp;gt; argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate Flyway DML migration for etl_config from YAML."
    )
    parser.add_argument(
        "config_type",
        choices=["etl_config"],
        help="Type of config to generate (currently only 'etl_config').",
    )
    parser.add_argument(
        "--name",
        required=True,
        help="Logical name for this config; used to pick YAML and to name the migration.",
    )
    parser.add_argument(
        "--yaml-file",
        help="Optional explicit path to YAML file. "
             "If omitted, defaults to CONFIG_DIR/&amp;lt;name&amp;gt;.yaml",
    )
    parser.add_argument(
        "--table",
        default="etl_config",
        help="Target database table name (default: etl_config).",
    )
    return parser.parse_args(argv)


def main(argv: Sequence[str]) -&amp;gt; int:
    args = parse_args(argv)

    # Determine which YAML file to load
    if args.yaml_file:
        yaml_path = Path(args.yaml_file).resolve()
    else:
        yaml_path = (CONFIG_DIR / f"{args.name}.yaml").resolve()

    print(f"Using YAML config: {yaml_path}")

    try:
        yaml_data = load_yaml(yaml_path)
        records = extract_records(yaml_data)
        sql_script = build_insert_sql(args.table, records)
    except Exception as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1

    filename = next_migration_filename(args.config_type, args.name)
    out_path = write_migration(sql_script, filename)

    print(f"Generated migration: {out_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>postgres</category>
      <category>automation</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <item>
      <title>merge jsonschema</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Tue, 28 Oct 2025 12:39:11 +0000</pubDate>
      <link>https://forem.com/devangular/merge-jsonschema-3667</link>
      <guid>https://forem.com/devangular/merge-jsonschema-3667</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@SuppressWarnings("unchecked")
private static Document innerJsonSchema(Document full) {
  // full is { "$jsonSchema": { ... } }
  return (Document) full.get("$jsonSchema");
}

private static Document mergeSchemasAsOneOf(Document... fullSchemas) {
  List&amp;lt;Document&amp;gt; versions = new ArrayList&amp;lt;&amp;gt;();
  for (Document full : fullSchemas) {
    versions.add(innerJsonSchema(full));
  }
  return new Document("$jsonSchema", new Document("oneOf", versions));
}

// If you prefer "anyOf":
private static Document mergeSchemasAsAnyOf(Document... fullSchemas) {
  List&amp;lt;Document&amp;gt; versions = new ArrayList&amp;lt;&amp;gt;();
  for (Document full : fullSchemas) {
    versions.add(innerJsonSchema(full));
  }
  return new Document("$jsonSchema", new Document("anyOf", versions));
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import org.bson.Document;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public final class SchemaDumper {
  private SchemaDumper() {}

  /** Dump the WHOLE validator doc: { "$jsonSchema": { ... } } */
  public static Path dumpValidator(String filenameWithoutExt, Document validator, Path outDir) throws Exception {
    Files.createDirectories(outDir);
    JsonWriterSettings pretty = JsonWriterSettings.builder()
        .indent(true)
        .outputMode(JsonMode.RELAXED)
        .build();
    String json = validator.toJson(pretty);
    Path file = outDir.resolve(filenameWithoutExt + ".json");
    Files.writeString(file, json, StandardCharsets.UTF_8);
    return file;
  }

  /** Dump only the INNER $jsonSchema object (no wrapper). */
  public static Path dumpInnerSchema(String filenameWithoutExt, Document validator, Path outDir) throws Exception {
    Document inner = validator.get("$jsonSchema", Document.class);
    if (inner == null) throw new IllegalArgumentException("Validator missing $jsonSchema");
    return dumpValidator(filenameWithoutExt, new Document("$jsonSchema", inner), outDir);
  }
}


Document cd18  = loadJsonSchema(CreditDecisioning.class, "{}");
Document cd20  = loadJsonSchema(CreditDecisioningV2.class, "{}");
Document merged = mergeSchemasAsAnyOf(cd18, cd20); // or oneOf

Path out = Path.of("target", "schemas");
SchemaDumper.dumpValidator("creditDecisioning-merged", merged, out);
// or, if someone wants only the inside object:
SchemaDumper.dumpInnerSchema("creditDecisioning-merged-inner", merged, out);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>data</category>
      <category>java</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>date conversion angular</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Wed, 22 Oct 2025 13:25:59 +0000</pubDate>
      <link>https://forem.com/devangular/date-conversion-angular-a86</link>
      <guid>https://forem.com/devangular/date-conversion-angular-a86</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What it’s doing (and why it shifts the date)
const dateObj = new Date(rawDateValue);                // if 'YYYY-MM-DD' → parsed as **UTC midnight**
const utcDate = new Date(Date.UTC(
  dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate()
));
const dateOnly = utcDate.toISOString().slice(0, 10);   // ISO in **UTC**
controlWithMetadata.control.setValue(dateOnly);        // now the control holds a **string**


If rawDateValue is 'YYYY-MM-DD', new Date('YYYY-MM-DD') is interpreted as UTC.

You then build another UTC date and call .toISOString() (still UTC).

Later, something (datepicker / form rebuild) re-parses that 'YYYY-MM-DD' string back to a Date. In JS, that string is again treated as UTC, but displayed in local time, so in US timezones it becomes the previous day.

Toggling edit mode runs this code again, compounding the effect → you observe -1 day (sometimes -2 around DST / multiple conversions).

Also, you’re changing the control type from a Date to a string, which makes the datepicker/serialization codepath even more fragile.

Minimal, targeted fix

Keep the control value as a Date (local midnight), never as a string; avoid toISOString() and avoid new Date('YYYY-MM-DD').

// Replace your cleanupDates implementation with this:
cleanupDates(taskAreaMember: TaskAreaMember) {
  if (taskAreaMember.fieldType !== 'date') return;

  taskAreaMember.controls.forEach((controlWithMetadata) =&amp;gt; {
    const v = controlWithMetadata.control.value;
    if (!v) return;

    let d: Date;

    if (v instanceof Date) {
      // Normalize to local midnight, preserving Y/M/D only.
      d = new Date(v.getFullYear(), v.getMonth(), v.getDate());
    } else if (typeof v === 'string') {
      // Expecting 'YYYY-MM-DD' → construct **local** date (NOT new Date(ymd))
      const [y, m, dStr] = v.split('-').map(Number);
      d = new Date(y, (m || 1) - 1, dStr || 1); // local midnight
    } else {
      return;
    }

    controlWithMetadata.control.setValue(d, { emitEvent: false });
  });
}

And when you build the payload (save/merge path)

Convert from Date → 'YYYY-MM-DD' explicitly, without toISOString():

function toYmd(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}

// wherever you currently read control.value for birth date:
const val = control.value as Date;
payload.birthDate = toYmd(val);

If you prefer Moment/Material UTC adapter

You can also standardize with Moment’s adapter and useUtc: true, but the core rule remains:

Never call new Date('YYYY-MM-DD')

Never use .toISOString() for birthdays or date-only values

Keep the form control as a Date (or a Moment) and serialize to 'YYYY-MM-DD' only at save time.

Why this will stop the drift

Local-midnight Date(y, m, d) has no timezone surprises when displayed.

You aren’t converting to ISO/UTC and back anymore.

The control type stays consistent (Date ↔ Date), so re-entering edit mode won’t re-interpret a string as UTC.

If you show me the exact save path where you read the birthdate control, I’ll give you the precise 2–3 line diff to swap in toYmd(...) there too.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>angular</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>help</category>
    </item>
    <item>
      <title>How to use crypto library in Nodejs</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Wed, 24 Sep 2025 13:11:49 +0000</pubDate>
      <link>https://forem.com/devangular/how-to-use-crypto-library-in-nodejs-327p</link>
      <guid>https://forem.com/devangular/how-to-use-crypto-library-in-nodejs-327p</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env node
/**
 * secret-encode.js
 *
 * Usage:
 *   node secret-encode.js encrypt "my secret text" myPassword
 *   node secret-encode.js encrypt-file ./input.txt myPassword
 *   node secret-encode.js decrypt &amp;lt;base64-string&amp;gt; myPassword
 *   node secret-encode.js decrypt-file &amp;lt;base64-string&amp;gt; myPassword ./out.txt
 *
 * Output format (JSON string):
 * {
 *   "v": 1,                 // version
 *   "salt": "&amp;lt;base64&amp;gt;",     // PBKDF2 salt
 *   "iv": "&amp;lt;base64&amp;gt;",       // AES-GCM IV
 *   "tag": "&amp;lt;base64&amp;gt;",      // AES-GCM auth tag
 *   "ct": "&amp;lt;base64&amp;gt;"        // ciphertext
 * }
 *
 * This is reversible: keep the password secret and share the encoded JSON string safely.
 */

const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');

const PBKDF2_ITER = 120_000; // iterations (adjust upward if you want more CPU cost)
const KEYLEN = 32; // 256-bit key
const SALT_LEN = 16;
const IV_LEN = 12; // recommended for GCM
const AUTH_TAG_LEN = 16;

function deriveKey(password, salt) {
  return crypto.pbkdf2Sync(password, salt, PBKDF2_ITER, KEYLEN, 'sha256');
}

function encryptBuffer(plainBuf, password) {
  const salt = crypto.randomBytes(SALT_LEN);
  const key = deriveKey(password, salt);
  const iv = crypto.randomBytes(IV_LEN);

  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: AUTH_TAG_LEN });
  const ct = Buffer.concat([cipher.update(plainBuf), cipher.final()]);
  const tag = cipher.getAuthTag();

  const out = {
    v: 1,
    salt: salt.toString('base64'),
    iv: iv.toString('base64'),
    tag: tag.toString('base64'),
    ct: ct.toString('base64'),
  };
  return JSON.stringify(out);
}

function decryptString(encryptedJsonStr, password) {
  let obj;
  try {
    obj = JSON.parse(encryptedJsonStr);
  } catch (e) {
    throw new Error('Input is not valid JSON. Make sure you pass the encoded JSON string.');
  }
  if (obj.v !== 1) throw new Error('Unsupported version');

  const salt = Buffer.from(obj.salt, 'base64');
  const iv = Buffer.from(obj.iv, 'base64');
  const tag = Buffer.from(obj.tag, 'base64');
  const ct = Buffer.from(obj.ct, 'base64');

  const key = deriveKey(password, salt);
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, { authTagLength: AUTH_TAG_LEN });
  decipher.setAuthTag(tag);

  const plain = Buffer.concat([decipher.update(ct), decipher.final()]);
  return plain;
}

async function run() {
  const args = process.argv.slice(2);
  if (args.length &amp;lt; 1) return printHelp();

  const cmd = args[0];

  try {
    if (cmd === 'encrypt') {
      if (args.length &amp;lt; 3) return printHelp();
      const text = args[1];
      const password = args[2];
      const out = encryptBuffer(Buffer.from(text, 'utf8'), password);
      console.log(out);
      return;
    }

    if (cmd === 'encrypt-file') {
      if (args.length &amp;lt; 3) return printHelp();
      const file = args[1];
      const password = args[2];
      const b = await fs.readFile(file);
      const out = encryptBuffer(b, password);
      console.log(out);
      return;
    }

    if (cmd === 'decrypt') {
      if (args.length &amp;lt; 3) return printHelp();
      const encoded = args[1];
      const password = args[2];
      const plainBuf = decryptString(encoded, password);
      console.log(plainBuf.toString('utf8'));
      return;
    }

    if (cmd === 'decrypt-file') {
      if (args.length &amp;lt; 4) return printHelp();
      const encoded = args[1];
      const password = args[2];
      const outFile = args[3];
      const plainBuf = decryptString(encoded, password);
      await fs.writeFile(outFile, plainBuf);
      console.log(`Wrote decrypted content to ${outFile}`);
      return;
    }

    printHelp();
  } catch (err) {
    console.error('Error:', err.message || err);
    process.exitCode = 2;
  }
}

function printHelp() {
  const me = path.basename(process.argv[1]);
  console.log(`
Usage:
  node ${me} encrypt "my secret text" myPassword
  node ${me} encrypt-file ./input.txt myPassword
  node ${me} decrypt '&amp;lt;encoded-json-string&amp;gt;' myPassword
  node ${me} decrypt-file '&amp;lt;encoded-json-string&amp;gt;' myPassword ./out.txt

Notes:
 - The output is a JSON string (salt, iv, tag, ciphertext in base64). Share that string publicly if you like.
 - NEVER share the password publicly. Send the password via a separate secure channel (phone, Signal, etc).
 - For short secrets you can use "encrypt", for files use "encrypt-file".
`);
}

if (require.main === module) run();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and how to decode&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node decrypt-encrypt.js decrypt '{"v":1,"salt":"YkgTYa8bQeuudUVyc6JzTg==","iv":"gYGLAUZFttrwNOHB","tag":"OhQSFgCQuR5iKMQnIh7Jgg==","ct":"43rxWQfb40QNZEEw01EXvDwAH/aRPz7ln6ZgBqah1vIYZGAxkKW4B/xzH/h3u7DEsGMg3kGwZZXTbNp3A/FEiyQhH9gWstmAwy7klJ+hIttCSBVR40WJR6zIKNjVbZDYeDSxsO0HqNGaY7E2gC/ampLwoefl3hHDAeyddsaSk9/4lwji0SYMjrlvFOcHZtMl9CDQiUzkpY7F8zH3TEsu2e8nZuLJojrz3+UEXTdMguGP3hU1YZEAiNfkdW27UiTfgDkHljxm7ZxLKgoe1DBvmnWKIFShixZ2ps1wBYdp6vOyrVv1mXPX3svYSXK5P+f/7WwiuH5QaaVrj1bGSgRmTuT9tfECmToABo9PCi9QoB0vKoRSA/zztTTcG9jSOrkk5GgCmS84r3lBNcZcNyJZ/Iy1IVv8dGJt0r9QjUjPdpGDf1NUTpbrEkUHXdnPpTw8EQNj1mrI88OldqX/xwjayV/2C0rLlS0tRKQCZ0sXSJPBomKL1uPbLGDW8sM6F0iNuMJHoXL6Hd+DvJnmaexDhnW4uHdlwkfM93B9Wqtlzg3wCB80ze1XsQLJml6uP63kbt6rlNG0il1HtuMDOn58oZYAmlciCZ+a/do5dbs7TuxLuOZjVEvh2D8vVA8DCAKT47r0QIaDcrBY8s15+bWJSTnTSVhIXCRs3L3UjINsbNGnvjQjFLpFLjl6El/gGr+9CNyNkmQ3mVEDtZtU8q+0ub6dPawthWzYwsdU7VFC8D04xsfmUBub7QZmWyWPM5BGSTfHITKCtNRwaQvgTyztDQ0fJY8DsK3+ShtdlxBrZY3i2O6PuKL6rEPBxIyqPx4faMwSmBKrvqAYJBgUTdMq6zQVJqugeqVJ7MCEwPDbFW2yUps4IjAqPQhcOQmQ7euSyW/MN3IRzjuqR6iVcv7axCmROK4NAuCNkDpbHHo3rAiJ8WDg1ByMqcM0yKuHUi3E="}' "your secret key"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>cli</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>mongodb shell script counting</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Sun, 21 Sep 2025 00:53:37 +0000</pubDate>
      <link>https://forem.com/devangular/mongodb-shell-script-counting-4j02</link>
      <guid>https://forem.com/devangular/mongodb-shell-script-counting-4j02</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ======= CONFIG: list your base collections here =======
const targetCollections = [
];

// ======= UTILITIES =======
function collExists(name) {
  return db.getCollectionInfos({ name }).length &amp;gt; 0;
}
function countOrZero(name) {
  if (!collExists(name)) return 0;
  // Accurate count; if you prefer faster (approx) use estimatedDocumentCount()
  return db.getCollection(name).countDocuments({});
}
function pad(str, len) {
  const s = String(str);
  return s + " ".repeat(Math.max(0, len - s.length));
}

// ======= RUN =======
const header =
  "Collection".padEnd(28) +
  "original".padEnd(12) +
  "duplicated_".padEnd(14) +
  "backup_".padEnd(12) +
  "backup_new_apps_".padEnd(18);

print(header);
print("-".repeat(header.length));

let totalOriginal = 0;
let totalDuplicated = 0;
let totalBackup = 0;
let totalBackupNewApps = 0;

for (const base of targetCollections) {
  const originalName = base;
  const duplicatedName = `duplicated_${base}`;
  const backupName = `backup_${base}`;
  const backupNewAppsName = `backup_new_apps_${base}`;

  const cOriginal = countOrZero(originalName);
  const cDup = countOrZero(duplicatedName);
  const cBackup = countOrZero(backupName);
  const cBackupNew = countOrZero(backupNewAppsName);

  totalOriginal += cOriginal;
  totalDuplicated += cDup;
  totalBackup += cBackup;
  totalBackupNewApps += cBackupNew;

  print(
    pad(base, 28) +
    pad(cOriginal, 12) +
    pad(cDup, 14) +
    pad(cBackup, 12) +
    pad(cBackupNew, 18)
  );
}

print("-".repeat(header.length));
print(
  pad("TOTAL", 28) +
  pad(totalOriginal, 12) +
  pad(totalDuplicated, 14) +
  pad(totalBackup, 12) +
  pad(totalBackupNewApps, 18)
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;db.application.countDocuments({&lt;br&gt;
  $or: [&lt;br&gt;
    { "applicants.0.firstName": { $exists: false } },&lt;br&gt;
    { "applicants.0.firstName": null },&lt;br&gt;
    { "applicants.0.firstName": "" },&lt;br&gt;
    { "applicants.0.lastName": { $exists: false } },&lt;br&gt;
    { "applicants.0.lastName": null },&lt;br&gt;
    { "applicants.0.lastName": "" }&lt;br&gt;
  ]&lt;br&gt;
});&lt;/p&gt;

</description>
    </item>
    <item>
      <title>NodeJs Mongo script query</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Sat, 20 Sep 2025 21:12:09 +0000</pubDate>
      <link>https://forem.com/devangular/nodejs-mongo-script-query-1j1b</link>
      <guid>https://forem.com/devangular/nodejs-mongo-script-query-1j1b</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// findAppIdsCreatedSinceFriday.js
// Usage: MONGO_URI="mongodb://..." node findAppIdsCreatedSinceFriday.js

const { MongoClient } = require("mongodb");

(async () =&amp;gt; {
  const uri = process.env.MONGO_URI || "mongodb://localhost:27017";
  const dbName = process.env.DB_NAME
  const collName = process.env.COLL_NAME;

  // 🔒 Hardcode the Friday midnight you want
  // If midnight in New York was intended and you're in EDT (UTC-4),
  // use the equivalent UTC time (04:00:00Z).
  // Example: Sep 19, 2025 00:00 in NYC -&amp;gt; 2025-09-19T04:00:00Z
  const FRIDAY_ISO_UTC = "2025-09-19T04:00:00Z";

  const boundary = new Date(FRIDAY_ISO_UTC);         // JS Date
  const boundaryISO = boundary.toISOString();        // For string comparisons

  const client = new MongoClient(uri);

  try {
    await client.connect();
    const coll = client.db(dbName).collection(collName);

    console.log("Boundary (UTC):", boundaryISO);

    // ✅ Works for both BSON Date and ISO string fields, no $convert/onError
    const filter = {
      $or: [
        // Case 1: field is a BSON Date
        { "createdTimeStamp.dateTime": { $gte: boundary } },

        // Case 2: field is an ISO string; lexicographic compare is valid for ISO-8601
        {
          $and: [
            { "createdTimeStamp.dateTime": { $type: "string" } },
            { "createdTimeStamp.dateTime": { $gte: boundaryISO } }
          ]
        }
      ]
    };

    const projection = { _id: 0, applicationId: 1 };

    const cursor = coll.find(filter, { projection }).batchSize(1000);

    let count = 0;
    for await (const doc of cursor) {
      if (doc &amp;amp;&amp;amp; doc.applicationId != null) {
        console.log(String(doc.applicationId));
        count++;
      }
    }
    console.log("Matched:", count);
  } catch (err) {
    console.error("Error:", err);
  } finally {
    await client.close();
  }
})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>database</category>
      <category>javascript</category>
      <category>mongodb</category>
      <category>node</category>
    </item>
    <item>
      <title>Mongo db index creation</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Sat, 20 Sep 2025 03:01:04 +0000</pubDate>
      <link>https://forem.com/devangular/mongo-db-index-creation-30c5</link>
      <guid>https://forem.com/devangular/mongo-db-index-creation-30c5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// mongosh

function getLastFridayNYMidnightUTC() {
  const now = new Date();
  const daysSinceFriday = (now.getDay() - 5 + 7) % 7; // Friday = 5
  return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysSinceFriday, 0, 0, 0, 0);
}

const lastFriday = getLastFridayNYMidnightUTC();
print("Last Friday (NY midnight) =", lastFriday.toISOString());

// Find all docs where dateTime &amp;gt; lastFriday
db.getCollection("app").find(
  {
    $expr: {
      $gte: [
        { $dateFromString: { dateString: "$timestamp.dateTime" } },
        lastFriday
      ]
    }
  },
  { _id: 1, "dateTime": 1 }
).forEach(printjson);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>database</category>
      <category>javascript</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Mongo db index creation</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Tue, 16 Sep 2025 19:43:54 +0000</pubDate>
      <link>https://forem.com/devangular/mongo-db-index-creation-k10</link>
      <guid>https://forem.com/devangular/mongo-db-index-creation-k10</guid>
      <description></description>
    </item>
    <item>
      <title>How to handle changes in angular tests</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Tue, 09 Sep 2025 15:47:22 +0000</pubDate>
      <link>https://forem.com/devangular/how-to-handle-changes-in-angular-tests-58ok</link>
      <guid>https://forem.com/devangular/how-to-handle-changes-in-angular-tests-58ok</guid>
      <description>&lt;h1&gt;
  
  
  Handling Change in Angular Tests (Without Losing Your Mind)
&lt;/h1&gt;

&lt;p&gt;Code changes. Requirements shift. Components get refactored. If your Angular tests shatter every time, they’re testing implementation details—not behavior. Here’s a practical, battle-tested guide to make your spec files &lt;strong&gt;easy to update, hard to break&lt;/strong&gt;, and quick to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  1) Adopt a “Public-Surface First” mindset
&lt;/h2&gt;

&lt;p&gt;Test &lt;strong&gt;inputs, outputs, and rendered behavior&lt;/strong&gt;, not internals.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer: “Given these &lt;code&gt;@Input()&lt;/code&gt;s, it renders X; when I click Y, it emits Z.”&lt;/li&gt;
&lt;li&gt;Avoid: “It calls private method A and sets private property B.”
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emits value on Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// helper that finds data-testid="save-btn" and clicks&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Refactors of internals don’t break tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  2) Stabilize DOM queries with test IDs (or Harnesses)
&lt;/h2&gt;

&lt;p&gt;DOM structures shift during refactors. CSS classes change. Your tests shouldn’t care.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add stable attributes: &lt;code&gt;data-testid="save-btn"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For Angular Material, prefer &lt;strong&gt;Component Test Harnesses&lt;/strong&gt;—they’re built for resilient querying.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-testid=&lt;/span&gt;&lt;span class="s"&gt;"save-btn"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"onSave()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getEl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-testid="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Renaming CSS or adding wrappers doesn’t break tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  3) Use Page Objects for complex components
&lt;/h2&gt;

&lt;p&gt;Page Objects centralize UI knowledge and keep specs readable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfilePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProfileComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nx"&gt;nameInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;clickSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-testid="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; When markup changes, you fix the page object once—not 30 tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  4) Keep DI/mocks thin and intentional
&lt;/h2&gt;

&lt;p&gt;Spin up the smallest possible TestBed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide only what you need.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;jasmine.createSpyObj&lt;/code&gt; (or &lt;code&gt;jest.fn()&lt;/code&gt;) for services.&lt;/li&gt;
&lt;li&gt;Prefer &lt;code&gt;HttpClientTestingModule&lt;/code&gt; over real HTTP.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;mockApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jasmine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSpyObj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ApiService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updateUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ReactiveFormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpClientTestingModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ProfileComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockApi&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Fewer dependencies → fewer broken tests when modules shuffle.&lt;/p&gt;




&lt;h2&gt;
  
  
  5) Master Angular async: &lt;code&gt;fakeAsync&lt;/code&gt;, &lt;code&gt;tick&lt;/code&gt;, &lt;code&gt;waitForAsync&lt;/code&gt;, and Observables
&lt;/h2&gt;

&lt;p&gt;Pick one style per spec and use it consistently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fakeAsync&lt;/code&gt; + &lt;code&gt;tick()&lt;/code&gt;&lt;/strong&gt;: great for timers, debounces, animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;waitForAsync&lt;/code&gt; + &lt;code&gt;fixture.whenStable()&lt;/code&gt;&lt;/strong&gt;: great for async init with Promises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;marbles&lt;/code&gt;&lt;/strong&gt; (optional): for complex RxJS flows.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debounces search input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;fakeAsync&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typeSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// debounceTime(300)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Timing changes are easy to adjust; tests don’t flake.&lt;/p&gt;




&lt;h2&gt;
  
  
  6) Trigger change detection deliberately
&lt;/h2&gt;

&lt;p&gt;Know when to call &lt;code&gt;fixture.detectChanges()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial render after creating component.&lt;/li&gt;
&lt;li&gt;After changing &lt;strong&gt;Inputs&lt;/strong&gt;, updating &lt;strong&gt;form controls&lt;/strong&gt;, or firing &lt;strong&gt;events&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;After pushing values into &lt;strong&gt;Subjects&lt;/strong&gt; in mocked services.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Predictable updates; fewer “why isn’t it rendering” moments.&lt;/p&gt;




&lt;h2&gt;
  
  
  7) Test Inputs &amp;amp; Outputs explicitly
&lt;/h2&gt;

&lt;p&gt;When code changes, this is what matters most.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders price with currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// cents&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getEl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$19.99&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forwards edit click to parent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jasmine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;getEl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8) Make HTTP tests change-proof with &lt;code&gt;HttpTestingController&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Don’t rely on real endpoints or interceptors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loads profile on init&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ngOnInit&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; Endpoint contract changes are one-line fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  9) Time, dates, and randomness: freeze them
&lt;/h2&gt;

&lt;p&gt;Refactors often change date formatting or default ranges. Freeze time &amp;amp; seeds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jasmine clock&lt;/strong&gt; or &lt;strong&gt;Jest fake timers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Override &lt;code&gt;Date.now&lt;/code&gt;, use &lt;strong&gt;test doubles&lt;/strong&gt; for date services.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;now&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-01-01T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; No flakiness from “today” logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  10) Feature flags and environment
&lt;/h2&gt;

&lt;p&gt;Test both sides of flags without rebuilding TestBed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FEATURE_FLAGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enableV2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Wrap flags in a service or injection token so tests can flip them cheaply.&lt;/p&gt;




&lt;h2&gt;
  
  
  11) Keep assertions behavior-centric and minimal
&lt;/h2&gt;

&lt;p&gt;Two or three strong assertions beat twenty brittle ones.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assert &lt;strong&gt;what the user sees/does&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Avoid asserting CSS class lists unless they represent behavior (e.g., “disabled”).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  12) When refactoring: protect with characterization tests
&lt;/h2&gt;

&lt;p&gt;Before changing old, messy code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a couple of tests that capture &lt;strong&gt;current&lt;/strong&gt; (even if weird) behavior.&lt;/li&gt;
&lt;li&gt;Refactor freely.&lt;/li&gt;
&lt;li&gt;Keep or modify tests to represent the &lt;strong&gt;desired&lt;/strong&gt; behavior.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; You never regress unintentionally.&lt;/p&gt;




&lt;h2&gt;
  
  
  13) Teardown &amp;amp; isolation
&lt;/h2&gt;

&lt;p&gt;Angular tears down automatically, but explicitly enabling teardown helps catch leaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Angular 16+ does good defaults; if needed:&lt;/span&gt;
&lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetTestingModule&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use fresh fixtures per test; avoid shared mutable state.&lt;/p&gt;




&lt;h2&gt;
  
  
  14) Organize tests like the component’s contract
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe(Component):
  - rendering (inputs)
  - interaction (outputs, clicks, keyboard)
  - async flows (loading, success, error)
  - edge cases (null/undefined, permissions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt; When behavior changes, you know exactly where to update.&lt;/p&gt;




&lt;h2&gt;
  
  
  15) Tooling tips that spare you time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Angular Testing Library (ATL)&lt;/strong&gt;: ergonomic queries &amp;amp; user events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material Harnesses&lt;/strong&gt;: stable access to Material components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage heatmaps&lt;/strong&gt;: add small, targeted tests to flip the last red lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch mode + focused tests&lt;/strong&gt;: &lt;code&gt;fit/it.only&lt;/code&gt; while iterating (don’t commit them).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Mini Example: making a brittle test resilient
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (brittle):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn.primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (resilient):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getEl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A short checklist when code changes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Did public inputs/outputs change? → Update tests to match the new contract.&lt;/li&gt;
&lt;li&gt;Did markup change? → Update Page Object or test harness selectors (tests stay the same).&lt;/li&gt;
&lt;li&gt;Did timing/debounce change? → Adjust &lt;code&gt;tick()&lt;/code&gt;/&lt;code&gt;fakeAsync()&lt;/code&gt; durations.&lt;/li&gt;
&lt;li&gt;Did services/endpoints change? → Update &lt;code&gt;HttpTestingController&lt;/code&gt; expectations only.&lt;/li&gt;
&lt;li&gt;Did feature flags flip? → Provide a different flag value in the spec.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Templates you can copy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data-test ID helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-testid="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spy service factory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createApiSpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jasmine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSpyObj&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApiService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ApiService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mocking Observables cleanly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;mockUserService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test behavior, not plumbing.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stabilize selectors&lt;/strong&gt; (test IDs / harnesses).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract test mechanics&lt;/strong&gt; (Page Objects, helpers).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control time &amp;amp; async.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep DI/mocks small.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do this and your Angular tests will survive refactors, design changes, and new requirements with minimal edits—exactly what you want when code inevitably evolves.&lt;/p&gt;

&lt;p&gt;If you want, send me one of your brittle specs and the component—it’s quick to show how to “harden” it using the patterns above.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Practical Strategy for Angular Testing</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Wed, 27 Aug 2025 13:17:16 +0000</pubDate>
      <link>https://forem.com/devangular/angular-test-strategy-46d8</link>
      <guid>https://forem.com/devangular/angular-test-strategy-46d8</guid>
      <description>&lt;p&gt;Testing in Angular is both a discipline and an investment. Done well, it ensures confidence in rapid releases, prevents regressions, and makes code easier to maintain. But writing effective tests isn’t just about hitting 80% coverage—it’s about designing a strategy that balances unit tests, integration tests, and end-to-end (E2E) tests while keeping tests fast, reliable, and meaningful.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Testing Pyramid for Angular&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A healthy Angular test suite should follow a pyramid-like structure:&lt;/p&gt;

&lt;p&gt;Unit Tests (foundation, ~70%)&lt;br&gt;
Fast, isolated, verifying a single component, service, or pipe.&lt;/p&gt;

&lt;p&gt;Integration Tests (~20%)&lt;br&gt;
Combine multiple Angular building blocks, e.g., a component with its service.&lt;/p&gt;

&lt;p&gt;End-to-End Tests (~10%)&lt;br&gt;
High-level UI flows using tools like Cypress or Playwright.&lt;/p&gt;

&lt;p&gt;The goal is not “all tests everywhere,” but “right tests at the right level.”&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unit Testing Best Practices
Services&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use HttpClientTestingModule with HttpTestingController to intercept API calls.&lt;/p&gt;

&lt;p&gt;Mock dependencies with spies (jasmine.createSpyObj) instead of real implementations.&lt;/p&gt;

&lt;p&gt;Test input → output: e.g., does saveBulkAction() call the correct URL with headers?&lt;/p&gt;

&lt;p&gt;it('should PUT with headers and task body', () =&amp;gt; {&lt;br&gt;
  service.saveBulkAction(payload, appId, true, false, 't-1', task).subscribe();&lt;/p&gt;

&lt;p&gt;const req = httpMock.expectOne(r =&amp;gt; r.urlWithParams.includes('application-bulk'));&lt;br&gt;
  expect(req.request.method).toBe('PUT');&lt;br&gt;
  expect(req.request.headers.get('Current-Entitlement')).toBe('ent-123');&lt;br&gt;
  expect(req.request.body.task).toEqual(task);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Components&lt;/p&gt;

&lt;p&gt;Use Angular’s TestBed and shallow render where possible.&lt;/p&gt;

&lt;p&gt;Stub child components with ng-mocks or NO_ERRORS_SCHEMA to avoid testing Angular itself.&lt;/p&gt;

&lt;p&gt;Focus on inputs, outputs, and template bindings.&lt;/p&gt;

&lt;p&gt;it('should emit value on button click', () =&amp;gt; {&lt;br&gt;
  const button = fixture.debugElement.query(By.css('button'));&lt;br&gt;
  spyOn(component.submit, 'emit');&lt;br&gt;
  button.nativeElement.click();&lt;br&gt;
  expect(component.submit.emit).toHaveBeenCalled();&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Pipes &amp;amp; Utilities&lt;/p&gt;

&lt;p&gt;Keep these simple: one test per logical branch is enough.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Integration Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sometimes you need to ensure multiple Angular parts work together:&lt;/p&gt;

&lt;p&gt;Test a component with its real service but mock only HTTP calls.&lt;/p&gt;

&lt;p&gt;Use TestBed.inject to bring in services and verify real Angular DI wiring.&lt;/p&gt;

&lt;p&gt;Useful for catching template + service mismatches.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;End-to-End Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unit and integration tests cover correctness, but E2E ensures the user journey works:&lt;/p&gt;

&lt;p&gt;Use Cypress or Playwright for realistic browser interactions.&lt;/p&gt;

&lt;p&gt;Keep E2E tests short and focused on critical flows (login, form submit, checkout).&lt;/p&gt;

&lt;p&gt;Run them in CI/CD nightly or pre-release; don’t block dev flow with slow suites.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Coverage vs. Confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Coverage is a metric, not a goal. Aim for meaningful coverage:&lt;/p&gt;

&lt;p&gt;Cover decision branches (if/else, error handling).&lt;/p&gt;

&lt;p&gt;Cover public APIs of services and components.&lt;/p&gt;

&lt;p&gt;Don’t waste time on trivial Angular boilerplate (e.g., ngOnInit with no logic).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Practical Tips&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LocalStorage / SessionStorage: Mock with spies in unit tests.&lt;/p&gt;

&lt;p&gt;Async testing: Prefer fakeAsync with tick() over done() callbacks.&lt;/p&gt;

&lt;p&gt;Error handling: Always test how services/components behave on error responses.&lt;/p&gt;

&lt;p&gt;Test doubles: Use factories for mock data (avoid copy-pasting JSON blobs).&lt;/p&gt;

&lt;p&gt;CI/CD: Fail fast—run unit tests on every push, E2E tests on merge to main.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Example Angular Testing Strategy (TL;DR)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unit tests for every service, pipe, and component (70%).&lt;/p&gt;

&lt;p&gt;Integration tests for components + services together (20%).&lt;/p&gt;

&lt;p&gt;E2E tests for top workflows only (10%).&lt;/p&gt;

&lt;p&gt;Focus on confidence, not coverage.&lt;/p&gt;

&lt;p&gt;Keep tests fast, isolated, and readable.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>testing</category>
      <category>softwaredevelopment</category>
      <category>tdd</category>
    </item>
    <item>
      <title>how to write angular post</title>
      <dc:creator>LS</dc:creator>
      <pubDate>Mon, 25 Aug 2025 22:28:23 +0000</pubDate>
      <link>https://forem.com/devangular/how-to-write-angular-post-4414</link>
      <guid>https://forem.com/devangular/how-to-write-angular-post-4414</guid>
      <description></description>
    </item>
  </channel>
</rss>
