diff --git a/actions/setup/js/update_entity_helpers.cjs b/actions/setup/js/update_entity_helpers.cjs index 61a353fb49d..3d79a676fa1 100644 --- a/actions/setup/js/update_entity_helpers.cjs +++ b/actions/setup/js/update_entity_helpers.cjs @@ -4,18 +4,42 @@ const { sanitizeTitle } = require("./sanitize_title.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); +/** + * @typedef {{ title?: string, body?: string, operation?: string }} EntityUpdateItem + */ + +/** + * @typedef {{ allow_body?: boolean, footer?: boolean | string }} EntityUpdateConfig + */ + +/** + * @typedef {{ _includeFooter: boolean, title?: string, _operation?: string, _rawBody?: string, body?: string }} EntityUpdateDataBase + */ + +/** + * @typedef {EntityUpdateDataBase & { [key: string]: any }} EntityUpdateData + */ + +/** + * @typedef {{ updateData: EntityUpdateData, hasCommonUpdates: boolean }} EntityUpdateResult + */ + /** * Build shared update payload fields for issue/PR update handlers. * - * @param {Object} item - * @param {Object} config - * @param {Object} options - * @param {boolean} [options.allowTitle=true] - * @param {string} [options.defaultOperation] - Required when item.body may be present; used as fallback operation if item.operation and configDefaultOperation are both absent. - * @param {string | undefined} [options.configDefaultOperation] - * @param {boolean} [options.includeBodyInApiData=false] - * @param {(() => void) | undefined} [options.onBodyDisallowed] - * @returns {{updateData: Object, hasCommonUpdates: boolean}} + * `options.defaultOperation` is required when `item.body` may be present; + * used as fallback when `item.operation` and `configDefaultOperation` are both absent. + * + * @param {EntityUpdateItem} item + * @param {EntityUpdateConfig} config + * @param {{ + * allowTitle?: boolean, + * defaultOperation?: string, + * configDefaultOperation?: string, + * includeBodyInApiData?: boolean, + * onBodyDisallowed?: (() => void), + * }} [options] + * @returns {EntityUpdateResult} */ function buildCommonEntityUpdateData(item, config, options = {}) { const { allowTitle = true, defaultOperation, configDefaultOperation, includeBodyInApiData = false, onBodyDisallowed } = options; diff --git a/actions/setup/js/update_entity_helpers.test.cjs b/actions/setup/js/update_entity_helpers.test.cjs index 28fe4538d38..7ab708344b1 100644 --- a/actions/setup/js/update_entity_helpers.test.cjs +++ b/actions/setup/js/update_entity_helpers.test.cjs @@ -80,4 +80,53 @@ describe("update_entity_helpers.cjs - buildCommonEntityUpdateData", () => { expect(result.updateData._rawBody).toBeUndefined(); expect(result.hasCommonUpdates).toBe(false); }); + + it("throws when body is present but no operation is resolvable", () => { + expect(() => buildCommonEntityUpdateData({ body: "Body text" }, {})).toThrow("buildCommonEntityUpdateData: defaultOperation is required when body may be present"); + }); + + it("returns hasCommonUpdates false when neither title nor body is present", () => { + const result = buildCommonEntityUpdateData({}, {}); + + expect(result.hasCommonUpdates).toBe(false); + expect(result.updateData.title).toBeUndefined(); + expect(result.updateData._rawBody).toBeUndefined(); + expect(result.updateData._includeFooter).toBe(true); + }); + + it("populates _includeFooter false when config.footer is false", () => { + const result = buildCommonEntityUpdateData({}, { footer: false }); + + expect(result.updateData._includeFooter).toBe(false); + }); + + it('populates _includeFooter false when config.footer is the string "false"', () => { + const result = buildCommonEntityUpdateData({}, { footer: "false" }); + + expect(result.updateData._includeFooter).toBe(false); + }); + + it("does not include body in updateData.body by default when includeBodyInApiData is omitted", () => { + const result = buildCommonEntityUpdateData({ body: "Body text" }, {}, { defaultOperation: "append" }); + + expect(result.updateData._rawBody).toBe("Body text"); + expect(result.updateData.body).toBeUndefined(); + }); + + it("handles title-only update without body operation", () => { + const result = buildCommonEntityUpdateData({ title: "Only title" }, {}); + + expect(result.updateData.title).toBe("Only title"); + expect(result.updateData._operation).toBeUndefined(); + expect(result.updateData._rawBody).toBeUndefined(); + expect(result.hasCommonUpdates).toBe(true); + }); + + it("does not call onBodyDisallowed when body is absent even if allow_body is false", () => { + const onBodyDisallowed = vi.fn(); + + buildCommonEntityUpdateData({}, { allow_body: false }, { onBodyDisallowed }); + + expect(onBodyDisallowed).not.toHaveBeenCalled(); + }); });