diff --git a/MIGRATION.md b/MIGRATION.md
index 15246311a53..52cb910ef59 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,5 +1,7 @@
Migration
+- [From version 6.4.x to 6.5.0](#from-version-64x-to-650)
+ - [CSF3 auto-title redundant filename](#csf3-auto-title-redundant-filename)
- [From version 6.3.x to 6.4.0](#from-version-63x-to-640)
- [Automigrate](#automigrate)
- [CRA5 upgrade](#cra5-upgrade)
@@ -188,6 +190,23 @@
- [Packages renaming](#packages-renaming)
- [Deprecated embedded addons](#deprecated-embedded-addons)
+## From version 6.4.x to 6.5.0
+
+### CSF3 auto-title redundant filename
+
+SB 6.4 introduced experimental "auto-title", in which a story's location in the sidebar (aka `title`) can be automatically inferred from its location on disk. For example, the file `atoms/Button.stories.js` might result in the title `Atoms/Button`.
+
+The heuristic failed in the common scenario in which each component gets its own directory, e.g. `atoms/Button/Button.stories.js`, which would result in the redundant title `Atoms/Button/Button`. Alternatively, `atoms/Button/index.stories.js` would result in `Atoms/Button/Index`.
+
+To address this problem, 6.5 introduces a new heuristic to removes the filename if it matches the directory name (case insensitive) or `index`. So `atoms/Button/Button.stories.js` and `atoms/Button/index.stories.js` would both result in the title `Atoms/Button`.
+
+Since CSF3 is experimental, we are introducing this technically breaking change in a minor release. If you desire the old structure, you can manually specify the title in file. For example:
+
+```js
+// atoms/Button/Button.stories.js
+export default { title: 'Atoms/Button/Button' };
+```
+
## From version 6.3.x to 6.4.0
### Automigrate
diff --git a/lib/store/src/autoTitle.test.ts b/lib/store/src/autoTitle.test.ts
index 7122e615100..ced6ade6a67 100644
--- a/lib/store/src/autoTitle.test.ts
+++ b/lib/store/src/autoTitle.test.ts
@@ -40,6 +40,24 @@ describe('autoTitle', () => {
).toMatchInlineSnapshot(`Atoms/To/File`);
});
+ it('match with trailing duplicate', () => {
+ expect(
+ auto(
+ './path/to/button/button.stories.js',
+ normalizeStoriesEntry({ directory: './path' }, options)
+ )
+ ).toMatchInlineSnapshot(`To/Button`);
+ });
+
+ it('match with trailing index', () => {
+ expect(
+ auto(
+ './path/to/button/index.stories.js',
+ normalizeStoriesEntry({ directory: './path' }, options)
+ )
+ ).toMatchInlineSnapshot(`To/Button`);
+ });
+
it('match with hyphen path', () => {
expect(
auto(
diff --git a/lib/store/src/autoTitle.ts b/lib/store/src/autoTitle.ts
index a46ba2aeed5..629617faa6b 100644
--- a/lib/store/src/autoTitle.ts
+++ b/lib/store/src/autoTitle.ts
@@ -10,8 +10,8 @@ interface NormalizedStoriesSpecifier {
importPathMatcher: RegExp;
}
-const stripExtension = (titleWithExtension: string) => {
- let parts = titleWithExtension.split('/');
+const stripExtension = (path: string[]) => {
+ let parts = [...path];
const last = parts[parts.length - 1];
const dotIndex = last.indexOf('.');
const stripped = dotIndex > 0 ? last.substr(0, dotIndex) : last;
@@ -20,11 +20,19 @@ const stripExtension = (titleWithExtension: string) => {
if (first === '') {
parts = rest;
}
- return parts.join('/');
+ return parts;
};
-const startCaseTitle = (title: string) => {
- return title.split('/').map(startCase).join('/');
+// deal with files like "atoms/button/{button,index}.stories.js"
+const removeRedundantFilename = (paths: string[]) => {
+ let prevVal: string;
+ return paths.filter((val, index) => {
+ if (index === paths.length - 1 && (val === prevVal || val === 'Index')) {
+ return false;
+ }
+ prevVal = val;
+ return true;
+ });
};
/**
@@ -48,7 +56,10 @@ export const autoTitleFromSpecifier = (fileName: string, entry: NormalizedStorie
if (importPathMatcher.exec(normalizedFileName)) {
const suffix = normalizedFileName.replace(directory, '');
const titleAndSuffix = slash(pathJoin([titlePrefix, suffix]));
- return startCaseTitle(stripExtension(titleAndSuffix));
+ let path = titleAndSuffix.split('/');
+ path = stripExtension(path).map(startCase);
+ path = removeRedundantFilename(path);
+ return path.join('/');
}
return undefined;
};