Repository: swagger-api/swagger-ui (js)

Issue:
Download link missing for payloads that are not application/octet-stream
### Q&A (please complete the following information)
 - OS: Windows
 - Browser: chrome
 - Version: latest
 - Method of installation: From NSwag
 - Swagger-UI version: latest
 - Swagger/OpenAPI version: OpenAPI 3.0

### Content & configuration

This PR, https://github.com/swagger-api/swagger-ui/pull/8579/files, caused a regression for payloads that are not `application/octet-stream`.

For example, given these Response Headers:
```
Content-Disposition: attachment; filename=users.csv; filename*=UTF-8''users.csv
Content-Length: 6460682
Content-Type: text/csv
```

the type of `content` on [this line](https://github.com/swagger-api/swagger-ui/blob/master/src/core/components/response-body.jsx#L67) is `string`, and therefore `content.size === undefined`.


### Screenshots

![image](https://github.com/swagger-api/swagger-ui/assets/135146799/a92372b5-9992-4378-90f6-fb0e5df029de)


Pull request:
fix: missing download button
<!--- Provide a general summary of your changes in the Title above -->

### Description
<!--- Describe your changes in detail -->

Fixes #9298

### Motivation and Context
#9298
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- Use the magic "Fixes #1234" format, so the issues are -->
<!--- automatically closed when this PR is merged. -->



### How Has This Been Tested?
I've added cypress test
<!--- Please describe in detail how you manually tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->



### Screenshots (if appropriate):



## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

### My PR contains... 
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] No code changes (`src/` is unmodified: changes to documentation, CI, metadata, etc.)
- [ ] Dependency changes (any modification to dependencies in `package.json`)
- [x] Bug fixes (non-breaking change which fixes an issue)
- [ ] Improvements (misc. changes to existing features)
- [ ] Features (non-breaking change which adds functionality)

### My changes...
- [ ] are breaking changes to a public API (config options, System API, major UI change, etc).
- [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.).
- [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
- [x] are not breaking changes.

### Documentation
- [x] My changes do not require a change to the project documentation.
- [ ] My changes require a change to the project documentation.
- [ ] If yes to above: I have updated the documentation accordingly.

### Automated tests
- [ ] My changes can not or do not need to be tested.
- [x] My changes can and should be tested by unit and/or integration tests.
- [x] If yes to above: I have added tests to cover my changes.
- [x] If yes to above: I have taken care to cover edge cases in my tests.
- [x] All new and existing tests passed.


Patch:
diff --git a/src/core/components/response-body.jsx b/src/core/components/response-body.jsx
index 1ed23405..4a1e1c21 100644
--- a/src/core/components/response-body.jsx
+++ b/src/core/components/response-body.jsx
@@ -60,11 +60,11 @@ export default class ResponseBody extends React.PureComponent {
 
     if (
       (/^application\/octet-stream/i.test(contentType) ||
-      (headers["Content-Disposition"] && /attachment/i.test(headers["Content-Disposition"])) ||
-      (headers["content-disposition"] && /attachment/i.test(headers["content-disposition"])) ||
-      (headers["Content-Description"] && /File Transfer/i.test(headers["Content-Description"])) ||
-      (headers["content-description"] && /File Transfer/i.test(headers["content-description"]))) &&
-      content.size > 0
+        (headers["Content-Disposition"] && /attachment/i.test(headers["Content-Disposition"])) ||
+        (headers["content-disposition"] && /attachment/i.test(headers["content-disposition"])) ||
+        (headers["Content-Description"] && /File Transfer/i.test(headers["Content-Description"])) ||
+        (headers["content-description"] && /File Transfer/i.test(headers["content-description"]))) &&
+      (content.size > 0 || content.length > 0)
     ) {
       // Download
 


Test patch:
diff --git a/test/unit/components/response-body.jsx b/test/unit/components/response-body.jsx
index 0d316b6e..8771905f 100644
--- a/test/unit/components/response-body.jsx
+++ b/test/unit/components/response-body.jsx
@@ -28,7 +28,6 @@ describe("<ResponseBody />", function () {
   it("renders ResponseBody as 'image/svg'", function () {
     props.contentType = "image/svg"
     const wrapper = shallow(<ResponseBody {...props} />)
-    console.warn(wrapper.debug())
     expect(wrapper.find("highlightCodeComponent").length).toEqual(0)
   })
 
@@ -36,17 +35,26 @@ describe("<ResponseBody />", function () {
     props.contentType = "text/plain"
     props.content = "test text"
     const wrapper = shallow(<ResponseBody {...props} />)
-    console.warn(wrapper.debug())
     expect(wrapper.find("highlightCodeComponent[canCopy]").length).toEqual(1)
   })
 
-  it("should render Download file link for non-empty response", function () {
+  it("should render Download file link for non-empty Blob response", function () {
     props.contentType = "application/octet-stream"
     props.content = new Blob(["\"test\""], { type: props.contentType })
     const wrapper = shallow(<ResponseBody {...props} />)
     expect(wrapper.text()).toMatch(/Download file/)
   })
 
+  it("should render Download file link for non-empty text response", function () {
+    props.contentType = "text/plain"
+    props.content = "test text"
+    props.headers = {
+      "Content-Disposition": "attachment; filename=\"test.txt\"",
+    }
+    const wrapper = shallow(<ResponseBody {...props} />)
+    expect(wrapper.text()).toMatch(/Download file/)
+  })
+
   it("should not render Download file link for empty response", function () {
     props.contentType = "application/octet-stream"
     props.content = new Blob()