{
  "synthetic": true,
  "execution": "Teaching record only; no live integration tested",
  "slug": "campaign-query-literal-plus-roundtrip",
  "group": "content-data",
  "title": [
    "A campaign link must preserve a literal plus sign",
    "推广链接中的加号，要能原样往返"
  ],
  "intro": [
    "A course named C++ or a partnership label A+B can be corrupted when a campaign URL is assembled by string concatenation. A plus sign in form-style query encoding can mean a space. Commission a round-trip test of the actual parameter values before using the link.",
    "课程名C++或合作标记A+B，可能在拼接推广URL时被改坏。表单式查询编码中的加号可表示空格。使用链接前，应验收真实参数值的编码、解码往返。"
  ],
  "facts": [
    [
      "URLSearchParams uses application/x-www-form-urlencoded conventions: spaces serialize as plus, and literal plus characters must be encoded when constructing values. Passing a full URL as a query string is not URL parsing.",
      "URLSearchParams采用表单式编码：空格序列化为加号，字面加号须在构造值时正确编码；把完整URL当查询字符串传入，不等于解析URL。",
      "https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams"
    ]
  ],
  "example": [
    [
      [
        "course=C++ as a value passed with set",
        "Serialize then parse",
        "course returns C++, not C plus two spaces"
      ],
      [
        "用set传入课程值C++",
        "序列化再解析",
        "仍是C++，不是C加两个空格"
      ]
    ],
    [
      [
        "Query text course=C++ constructed manually",
        "Parse as form-style query",
        "Decodes plus signs as spaces; fails intended identity"
      ],
      [
        "手工拼查询course=C++",
        "按表单查询解析",
        "加号成为空格，不符合原课程身份"
      ]
    ],
    [
      [
        "partner=A+B and label=Spring sale",
        "Serialize two separate values",
        "A%2BB and Spring+sale; both decode correctly"
      ],
      [
        "partner=A+B，label=Spring sale",
        "分参数序列化",
        "A%2BB及Spring+sale，均能正确解码"
      ]
    ],
    [
      [
        "destination=https://example.test/?a=1&b=2",
        "Store destination as one query value",
        "Nested ampersand must not become a second outer parameter"
      ],
      [
        "目标https://example.test/?a=1&b=2",
        "作为一个查询值保存",
        "内层&不得变成第二个外层参数"
      ]
    ]
  ],
  "steps": [
    [
      "Write the intended values separately from the final URL. Use opaque non-personal campaign labels and an allowlisted destination; do not place customer emails or secrets in a query.",
      "先独立写出预期值，再组URL；使用不含个人信息的标记及获准目标，不在查询中放邮箱或秘密。"
    ],
    [
      "Construct parameters through the URL APIs, then parse the complete URL and compare every recovered value and parameter count with the original.",
      "通过URL接口构造参数，再解析完整地址，逐项比较恢复值及参数数量。"
    ],
    [
      "Have the receiving website or authorised test endpoint display the decoded test values. Distinguish link integrity from analytics attribution and actual enquiries.",
      "让接收网站或获准测试接口显示解码测试值；链接完整性与分析归因、真实询盘分开。"
    ]
  ],
  "result": [
    "A%2BB and A+B carry different raw query meanings under this convention. Visual inspection of the address bar may miss the distinction. The nested destination row also catches a common splitter defect without sending a request to any third party.",
    "按该约定，A%2BB与A+B的原始查询含义不同，目看地址栏可能漏掉；嵌套目标例也能在不向第三方请求的情况下发现拆参数错误。"
  ],
  "accept": [
    "Every decoded value and the outer parameter count must match the intended record. A correctly encoded link is not evidence that its campaign produced a sale.",
    "每个解码值及外层参数数量须与预期一致；编码正确不证明活动带来成交。"
  ],
  "limit": [
    "Synthetic labels and example.test URLs. No campaign submission, external message, analytics change or tracking-code installation is performed.",
    "合成标记及example.test地址，未提交推广、发外部消息、改分析或安装追踪代码。"
  ],
  "oldLinks": []
}
