{"record":{"id":"ac82d9967a25e533","repo":"vitest-dev/vitest","slug":"expected-is-not-a-string","errorCode":null,"errorMessage":"Expected is not a string","messagePattern":"Expected is not a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/expect/src/jest-asymmetric-matchers.ts","lineNumber":77,"sourceCode":"}\n\n// implement custom chai/loupe inspect for better AssertionError.message formatting\n// https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29\n// @ts-expect-error computed properties is not supported when isolatedDeclarations is enabled\n// FIXME: https://github.com/microsoft/TypeScript/issues/61068\nAsymmetricMatcher.prototype[Symbol.for('chai/inspect')] = function (options: { depth: number; truncate: number }): string {\n  // minimal pretty-format with simple manual truncation\n  const result = stringify(this, options.depth, { min: true })\n  if (result.length <= options.truncate) {\n    return result\n  }\n  return `${this.toString()}{…}`\n}\n\nexport class StringContaining extends AsymmetricMatcher<string> {\n  constructor(sample: string, inverse = false) {\n    if (!isA('String', sample)) {\n      throw new Error('Expected is not a string')\n    }\n\n    super(sample, inverse)\n  }\n\n  asymmetricMatch(other: string): boolean {\n    const result = isA('String', other) && other.includes(this.sample)\n\n    return this.inverse ? !result : result\n  }\n\n  toString() {\n    return `String${this.inverse ? 'Not' : ''}Containing`\n  }\n\n  getExpectedType() {\n    return 'string'\n  }","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/expect/src/jest-asymmetric-matchers.ts#L59-L95","documentation":"The StringContaining asymmetric matcher (expect.stringContaining) requires its sample to be a string; isA('String', sample) returns false for any non-string and the constructor throws. This is a fail-fast construction-time check, not a match-time one.","triggerScenarios":"expect.stringContaining(sample) called with sample that is a number, object, null, etc.","commonSituations":"Passing a variable whose type is not narrowed (e.g. a config value typed as string | number); passing a RegExp where a substring was expected (use stringMatching instead).","solutions":["Pass a plain string substring to expect.stringContaining.","If you need pattern matching, use expect.stringMatching(regexOrString).","Coerce or validate the value is a string before passing: String(sample) or a type guard."],"exampleFix":"// before\nexpect(result).toEqual(expect.stringContaining(404))\n// after\nexpect(result).toEqual(expect.stringContaining('Not Found'))","handlingStrategy":"type-guard","validationCode":"function asStringContaining(sample) {\n  if (typeof sample !== 'string') {\n    throw new TypeError('expect.stringContaining needs a string')\n  }\n  return expect.stringContaining(sample)\n}","typeGuard":"function isString(v): v is string {\n  return typeof v === 'string'\n}","tryCatchPattern":null,"preventionTips":["Use expect.stringContaining only with string literals.","Reach for expect.stringMatching when a RegExp is needed.","Add a typeof check when the value comes from untyped config."],"tags":["expect","asymmetric-matcher","type-error"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}