かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その54 )( webpack を 3.8.1 → 4.9.1 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その53 )( Gradle を 3.5 → 4.6 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • webpack を 3.8.1 → 4.9.1 へバージョンアップします。

参照したサイト・書籍

目次

  1. webpack を 3.8.1 → 4.9.1 へバージョンアップする
  2. npm run build コマンドを実行してみる
  3. webpack-cli をインストールする
  4. 再び npm run build コマンドを実行してみる
  5. uglifyjs-webpack-plugin を 1.2.2 → 1.2.5 へバージョンアップする
  6. webpack.config.js を変更する
  7. package.json を変更する
  8. cross-env をアンインストールする
  9. 動作確認
  10. webpack.config.js から optimization、devtool の設定を取り除くとどうなるのか?

手順

webpack を 3.8.1 → 4.9.1 へバージョンアップする

npm install --save-dev webpack@4.9.1 コマンドを実行します。

f:id:ksby:20180527204920p:plain

npm run build コマンドを実行してみる

バージョンアップしただけで webpack.config.js は何も修正していない状態で npm run build コマンドを実行してみます。

f:id:ksby:20180527211011p:plain

CLI が別のパッケージになったので webpack-cli か webpack-command をインストールするようメッセージが出力されました。webpack-cli と webpack-command の違いは webpack-command サイト内の Differences With webpack-cli に記述があります。

今回は webpack-cli をインストールします。

webpack-cli をインストールする

npm install --save-dev webpack-cli コマンドを実行します。

f:id:ksby:20180527211732p:plain

再び npm run build コマンドを実行してみる

再び npm run build コマンドを実行してみます。

f:id:ksby:20180527212056p:plain f:id:ksby:20180527212149p:plain

webpack 4 から追加された --mode オプションを指定していないので、WARNING のメッセージが出力されました。

webpack 3 → 4 へのマイグレーション関連の記事を Web で調べて、以下の点を変更することにします。

  • webpack を実行している npm scripts に --mode オプションを追加します。
  • cross-env パッケージが不要になるのでアンインストールします。
  • webpack 4 では --mode development が指定されている時には最適化処理が実行されず、--mode production が指定されている時には最適化処理が実行されるようになったので、Uglify の設定を変更します。
  • uglifyjs-webpack-plugin をインストールしなくても --mode production が指定されていると Uglify されるのですが、デフォルトの設定では console.log() が削除されないようなので、uglifyjs-webpack-plugin をインストールしたままにして独自に設定することにします。また uglifyjs-webpack-plugin を最新版にバージョンアップします。

uglifyjs-webpack-plugin を 1.2.2 → 1.2.5 へバージョンアップする

npm install --save-dev uglifyjs-webpack-plugin@1.2.5 コマンドを実行します。

f:id:ksby:20180528014124p:plain

webpack.config.js を変更する

webpack.config.js の以下の点を変更します。

const webpack = require("webpack");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

// --mode オプションで指定された文字列を参照したい場合には argv.mode を参照する
module.exports = (env, argv) => {
    return {
        entry: {
            "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
            "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
            "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
            "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
        },
        output: {
            path: __dirname + "/src/main/resources/static",
            publicPath: "/",
            filename: "[name].js"
        },
        resolve: {
            modules: [
                "node_modules",
                "src/main/assets/js"
            ],
            alias: {
                jquery: "jquery"
            }
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: [
                        /node_modules/,
                        /jquery.autoKana.js$/
                    ],
                    loader: "eslint-loader"
                }
            ]
        },
        optimization: {
            minimizer: [
                new UglifyJsPlugin({
                    uglifyOptions: {
                        compress: true,
                        ecma: 5,
                        output: {
                            comments: false
                        },
                        compress: {
                            dead_code: true,
                            drop_console: true
                        }
                    },
                    sourceMap: false
                })
            ]
        },
        plugins: [
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery"
            })
        ],
        devtool: "inline-source-map"
    };
};
  • const isProduct = process.env.NODE_ENV === "product"; を削除します。
  • --mode オプションで指定されたモードを参照できるようにするために、module.exports = { ... };module.exports = (env, argv) => { return { ... }; }; に変更します。これで argv.mode--mode オプションで指定された文字列を参照できるようになります。
  • optimization: { ... } を追加し、ここに UglifyJsPlugin の設定を記述します。optimization: { ... } の設定は --mode development の時には適用されず(Uglify や sourceMap出力が行われません)、--mode production の時のみ適用されるようです(Uglify や sourceMap出力が行われます)。また ecma: 6 にすると IE11 で入力画面2の郵便番号を入力してヒットした候補をドロップダウンリストに表示する機能が動作しなかったので、ecma: 5 にします。
  • plugins: [ ... ] から .concat(isProduct ? [new UglifyJsPlugin()] : [] を削除します。
  • devtool: "inline-source-map" は残します。残しておかないとソースが変わりすぎて Chrome で debug しにくそうに思えたからです。optimization: { ... } で UglifyJsPlugin の設定も記述しておくと --mode production の時に sourceMap が残りません。

ちなみに console.log() が残ることを気にしなければ、optimization: { ... } の設定がなくても --mode production を指定すれば Uglify は行われます。

package.json を変更する

package.json の以下の点を変更します。

  "scripts": {
    ..........
    "webpack:build": "webpack --mode production",
    "webpack:watch": "webpack --mode development --watch",
    ..........
    "build": "run-s clean:cssjs-dir postcss:build webpack:build"
  },
  • webpack:build のコマンドを webpackwebpack --mode production に変更します。
  • webpack:watch のコマンドを webpack --watchwebpack --mode development --watch に変更します。
  • build のコマンドを cross-env NODE_ENV=product run-s ...run-s ... に変更します。

cross-env をアンインストールする

npm uninstall --save-dev cross-env コマンドを実行します。

f:id:ksby:20180529004519p:plain

動作確認

tomcat を起動した後、npm run springboot コマンドを実行してみます。

f:id:ksby:20180529012848p:plain f:id:ksby:20180529012900p:plain

生成された js ファイルを見るとファイルサイズが全て 700KB 以上あり、ファイルを開いてみても Uglify はされておらず、ソースはほぼそのまま残っています。

f:id:ksby:20180529013137p:plain

http://localhost:9080/inquiry/input/01/ にアクセスして入力画面1~3まで操作してみましたが、特に問題はありませんでした。

今度は npm run build コマンドを実行してみます。

f:id:ksby:20180529015031p:plain f:id:ksby:20180529015205p:plain

生成された js ファイルがほぼ 100KB 以下になり、ファイルを開いてみると Uglify されています。

f:id:ksby:20180529015339p:plain

http://localhost:8080/inquiry/input/01/ にアクセスして(ポート番号を 9080 → 8080 に変更して Tomcat に直接アクセスしています)入力画面1~3まで操作してみましたが、特に問題はありませんでした。

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy の失敗するテストをコメントアウトした後、clean タスク実行 → Rebuild Project → build タスクを実行してみます。

f:id:ksby:20180602124044p:plain

BUILD SUCCESSFUL のメッセージが出力されました。

問題なさそうですので、このまま 4.9.1 を使います。書き終わった時には 4.10.2 までバージョンが上がっていましたが。。。

webpack.config.js から optimization、devtool の設定を取り除くとどうなるのか?

optimization、devtool の設定を書かない時の動作もメモしておきます。

webpack.config.js から optimization、devtool の設定を削除します。

        ..........
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: [
                        /node_modules/,
                        /jquery.autoKana.js$/
                    ],
                    loader: "eslint-loader"
                }
            ]
        },
        plugins: [
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery"
            })
        ]
    };
};

まずは --mode development である npm run springboot を実行してみます。

f:id:ksby:20180602125908p:plain f:id:ksby:20180602130010p:plain

optimization、devtool の設定を入れていた時は生成された js ファイルは 700KB以上ありましたが、今回は 300~400KB程度になりました。

生成された js ファイルを開いてみると、実行コードは改行コードが \r\n に変換されて eval( ... ); で囲まれるようです。

f:id:ksby:20180602130454p:plain

Tomcat を起動してから、Chromehttp://localhost:8080/inquiry/input/01/ にアクセスして DevTools で input01.js を開いてみると、eval( ... ); のまま表示されて、これだと debug をどうやればよいのかちょっと分かりませんでした。

f:id:ksby:20180602131630p:plain

sourceMap もたぶん出力されていませんね。ファイルサイズが小さいのはその辺が理由でしょう。

次に --mode production である npm run build を実行してみます。

f:id:ksby:20180602132306p:plain f:id:ksby:20180602132429p:plain

ファイルサイズは optimization、devtool の設定を入れていた時より数KBだけ大きいです。

生成された js ファイルを開いてみると、Uglify されていますがコメントが完全に削除されずに一部残っていました。

f:id:ksby:20180602132723p:plain

Tomcat を起動してから、Chromehttp://localhost:8080/inquiry/input/01/ にアクセスして動作確認してみましたが、IE11 でも入力画面2の郵便番号を入力してヒットした候補をドロップダウンリストに表示する機能が動作しましたし、他にも問題はありませんでした。

また console.log(...); も入れていると削除されません(これは実際に書いて試してみました)。

今のところ、個人的には optimization、devtool の設定は書く方が好みですね。webpack 4 の設定がまだよく分かっていないだけかもしれませんが。

履歴

2018/06/02
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その53 )( Gradle を 3.5 → 4.6 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その52 )( 入力画面3を作成する5 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Gradle を 3.5 → 4.6 へバージョンアップします。

参照したサイト・書籍

目次

  1. build タスク実行時に出たエラーを解消する
    1. 'The String literal "セ??トされるはず?????ータがセ??トされて??ません" appears 5 times in this file; the first occurrence is on line 91'
  2. Gradle を 3.5 → 4.6 へバージョンアップする

手順

build タスク実行時に出たエラーを解消する

Gradle のバージョンアップの前に clean タスク実行 → Rebuild Project 実行 → build タスクを実行したところ、PMD の警告が出ていたので解消します。

f:id:ksby:20180527161213p:plain

'The String literal "セ??トされるはず?????ータがセ??トされて??ません" appears 5 times in this file; the first occurrence is on line 91'

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の中で throw new IllegalArgumentException("セットされるはずのデータがセットされていません"); とエラーメッセージの文字列を直接記述していたのが原因なので、定数文字列に変更します。

src/main/resources/messages_ja_JP.properties の以下の点を変更します。

InquiryInputController.validate.form.error=セットされるはずのデータがセットされていません
InquiryInput02Form.zipcode.UnmatchPattern=郵便番号が数字7桁ではありません。
..........
  • InquiryInputController.validate.form.error=セットされるはずのデータがセットされていません を追加します。

https://github.com/ksby/ksbysample-webapp-lending/blob/1.0.x/src/main/java/ksbysample/webapp/lending/helper/message/MessagesPropertiesHelper.java をコピーして src/main/java/ksbysample/webapp/bootnpmgeb/helper/message/MessagesPropertiesHelper.java に配置します。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の以下の点を変更します。

@Slf4j
@Controller
@RequestMapping("/inquiry/input")
@SessionAttributes("sessionData")
public class InquiryInputController {

    ..........

    private static final String VALIDATE_FORM_ERROR = "InquiryInputController.validate.form.error";

    private final ModelMapper modelMapper;

    private final InquiryInput02FormValidator inquiryInput02FormValidator;

    private final Validator mvcValidator;

    private final MessagesPropertiesHelper mph;

    /**
     * コンストラクタ
     *
     * @param modelMapper                 {@link ModelMapper} オブジェクト
     * @param inquiryInput02FormValidator {@link InquiryInput02FormValidator} オブジェクト
     * @param mvcValidator                {@link Validator} オブジェクト
     * @param mph                         {@link MessagesPropertiesHelper} オブジェクト
     */
    public InquiryInputController(ModelMapper modelMapper
            , InquiryInput02FormValidator inquiryInput02FormValidator
            , Validator mvcValidator
            , MessagesPropertiesHelper mph) {
        this.modelMapper = modelMapper;
        this.inquiryInput02FormValidator = inquiryInput02FormValidator;
        this.mvcValidator = mvcValidator;
        this.mph = mph;
    }

    ..........

    /**
     * 入力画面1 「次へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/01", params = {"move=next"})
    public String input01MoveNext(@Validated InquiryInput01Form inquiryInput01Form
            , BindingResult bindingResult
            , SessionData sessionData
            , UriComponentsBuilder builder) {
        if (bindingResult.hasErrors()) {
            bindingResult.getAllErrors().stream().forEach(e -> log.warn(e.getCode()));
            throw new IllegalArgumentException(mph.getMessage(VALIDATE_FORM_ERROR, null));
        }

        ..........
    }

    ..........
  • private static final String VALIDATE_FORM_ERROR = "InquiryInputController.validate.form.error"; を追加します。
  • private final MessagesPropertiesHelper mph; を追加します。
  • コンストラクタの引数に MessagesPropertiesHelper mph を、メソッド内に this.mph = mph; を追加します。
  • throw new IllegalArgumentException("セットされるはずのデータがセットされていません");throw new IllegalArgumentException(mph.getMessage(VALIDATE_FORM_ERROR, null)); に変更します(全部で5ヶ所)。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して PMD の警告が出力されなくなったことを確認します(画面キャプチャは省略します)。

Gradle を 3.5 → 4.6 へバージョンアップする

build.gradle の以下の点を変更します。

task wrapper(type: Wrapper) {
    gradleVersion = '4.6'
}
  • gradleVersion = '3.5'gradleVersion = '4.6' に変更します。

コマンドプロンプトを起動し、gradlew wrapper コマンドを実行します。

f:id:ksby:20180527171636p:plain

gradle/wrapper/gradle-wrapper.properties を開くと gradle-4.6-bin.zip に変更されています。

f:id:ksby:20180527171841p:plain

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

必ず失敗させているテストが1つあるので無効にします。src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy の以下の点を変更します。

/*
        @Test
        void "項目全てに入力して次へボタンをクリックすると確認画面へ遷移し、前の画面へ戻るボタンを押して入力画面3へ戻ると以前入力したデータがセットされて表示される"() {
            expect:
            assert false, "確認画面を実装してからテストを作成する"
        }
*/
  • void "項目全てに入力して次へボタンをクリックすると確認画面へ遷移し、前の画面へ戻るボタンを押して入力画面3へ戻ると以前入力したデータがセットされて表示される"() { ... } のテストをコメントアウトします。

clean タスク実行 → Rebuild Project → build タスクを実行します。。。が findbugsMain タスクでエラーになりました。

f:id:ksby:20180527173252p:plain

エラーが発生している Build file 'C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\build.gradle' line: 87 は以下の部分で、> No signature of method: org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.exclude() is applicable for argument types: (java.lang.String) values: [**/*.properties] というエラーメッセージが出ています。

f:id:ksby:20180527173523p:plain

tasks.withType(FindBugs) { ... }doFirst { ... } の部分はさすがに Gradle 4.x 以降になったら不要になっていると思いたいので、一旦削除してみます。

tasks.withType(FindBugs) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

clean タスク実行 → Rebuild Project → build タスクを実行すると、今度は BUILD SUCCESSFUL のメッセージが出力されました。

f:id:ksby:20180527175209p:plain

コンソールに出力されていた https://docs.gradle.org/4.6/userguide/command_line_interface.html#sec:command_line_warnings を見ると、By default, Gradle won’t display all warnings (e.g. deprecation warnings). という記述がありました。これで FindBugs Plugin の warnings が出なくなっているようです。Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0. のメッセージが出ていますが、 5.0 にバージョンアップする時にまた考えることにします。

問題なさそうですので、このまま 4.6 を使います。src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy のコメントアウトしたテストは元に戻します。

履歴

2018/05/27
初版発行。

IntelliJ IDEA を 2018.1.2 → 2018.1.4 へバージョンアップ

IntelliJ IDEA を 2018.1.2 → 2018.1.4 へバージョンアップする

IntelliJ IDEA の 2018.1.4 がリリースされているのでバージョンアップします。

IntelliJ IDEA 2018.1.4 is released! の記事を読むと、Java SE を 8u162 → 8u172 へ、IntelliJ IDEA を 2017.3.4 → 2017.3.5 → 2018.1.2 へ、Git for Windows を 2.16.2 → 2.17.0 へバージョンアップ に書いた 「Working directory」に $MODULE_DIR$ がセットされるようになっていた件について変更が入ったようです。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。マイナーバージョンアップなのに左下に「Update and Restart」ボタンではなく「Download」ボタンが表示されていますね。

    f:id:ksby:20180527095506p:plain

    「Download」ボタンを押して IntelliJ IDEA の Download ページを開いた後、「DOWNLOAD」ボタンを押して ideaIU-2018.1.4.exe をダウンロードします。

    f:id:ksby:20180527095722p:plain

  3. 起動している IntelliJ IDEA を終了します。

  4. ideaIU-2018.1.4.exe を実行します。

  5. IntelliJ IDEA Setup」ダイアログが表示されます。「Next >」ボタンをクリックします。

    f:id:ksby:20180527100953p:plain

  6. 「Uninstall old versions」画面が表示されます。画面上の全てのチェックボックスをチェックした後、「Next >」ボタンをクリックします。

    f:id:ksby:20180527101056p:plain

  7. 「Choose Install Location」画面が表示されます。「Destination Folder」を C:\IntelliJ_IDEA\2018.1.4 に変更した後、「Next >」ボタンをクリックします。

    f:id:ksby:20180527101215p:plain

  8. 「Installation Options」画面が表示されます。何も変更せずに「Next >」ボタンをクリックします。

    f:id:ksby:20180527101308p:plain

  9. 「Choose Start Menu Folder」画面が表示されます。何も変更せずに「Install」ボタンをクリックします。

  10. 「Installing」画面が表示されてインストールが始まりますので、完了するまで待ちます。

  11. インストールが完了すると「Completing IntelliJ IDEA Setup」画面が表示されます。「Finish」ボタンをクリックしてダイアログを閉じます。

  12. 今回は C:\IntelliJ_IDEA\2018.1.2 ディレクトリは削除されていました。

  13. C:\IntelliJ_IDEA\2018.1.4\bin\idea64.exe を実行します。

  14. IntelliJ IDEA のメイン画面が表示され画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20180527101906p:plain

  15. Plugin が全て最新にアップデートされていないようなので先にアップデートします。IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  16. IDE and Plugin Updates」ダイアログが表示されます。何も変更せずに「Update」ボタンをクリックします。

    f:id:ksby:20180527102035p:plain

    Patch がダウンロードされた後、IntelliJ IDEA を再起動します。再起動後、画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

  17. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.1.4 へバージョンアップされていることを確認します。

  18. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20180527102720p:plain

  19. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20180527103552p:plain

  20. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20180527104105p:plain

  21. 最後に「Working directory」に $MODULE_DIR$ がセットされる件を 新規Projectを作成して確認してみたところ、$MODULE_DIR$ がセットされたままでした。。。? セットされるのは変わらないようです。

    f:id:ksby:20180527105104p:plain

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その52 )( 入力画面3を作成する5 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その51 )( 入力画面3を作成する4 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面3の作成
    • サーバ側のテストを作成します。

参照したサイト・書籍

目次

  1. InquiryInput03FormNotEmptyRule クラスのテストを作成する
  2. InquiryInputController クラスのテストを変更する
  3. 次回は。。。

手順

InquiryInput03FormNotEmptyRule クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput03FormNotEmptyRule.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20180526172038p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput03FormNotEmptyRuleTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry.form

import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values
import spock.lang.Specification
import spock.lang.Unroll

import javax.validation.ConstraintViolation
import javax.validation.Validation

class InquiryInput03FormNotEmptyRuleTest extends Specification {

    def validator
    def inquiryInput03FormNotEmptyRule

    def setup() {
        validator = Validation.buildDefaultValidatorFactory().getValidator()
        inquiryInput03FormNotEmptyRule = new InquiryInput03FormNotEmptyRule(
                type1: Type1Values.PRODUCT.value
                , type2: [Type2Values.ESTIMATE.value, Type2Values.OTHER.value]
                , inquiry: "これはテストです"
                , survey: ["1", "2"]
        )
    }

    @Unroll
    def "type1 の NotEmpty のテスト(#type1 --> #size)"() {
        setup:
        inquiryInput03FormNotEmptyRule.type1 = type1
        Set<ConstraintViolation<InquiryInput03FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput03FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        type1                     || size
        ""                        || 1
        Type1Values.PRODUCT.value || 0
    }

    @Unroll
    def "type2 の NotEmpty のテスト(#type2 --> #size)"() {
        setup:
        inquiryInput03FormNotEmptyRule.type2 = type2
        Set<ConstraintViolation<InquiryInput03FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput03FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        type2                                                                              || size
        []                                                                                 || 1
        [Type2Values.ESTIMATE.value]                                                       || 0
        [Type2Values.ESTIMATE.value, Type2Values.CATALOGUE.value, Type2Values.OTHER.value] || 0
    }

    @Unroll
    def "inquiry の NotEmpty のテスト(#inquiry --> #size)"() {
        setup:
        inquiryInput03FormNotEmptyRule.inquiry = inquiry
        Set<ConstraintViolation<InquiryInput03FormNotEmptyRule>> constraintViolations =
                validator.validate(inquiryInput03FormNotEmptyRule)

        expect:
        constraintViolations.size() == size

        where:
        inquiry || size
        ""      || 1
        "テスト"   || 0
    }

}

テストを実行して全て成功することを確認します。

f:id:ksby:20180526181552p:plain

InquiryInputController クラスのテストを変更する

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy の以下の点を変更します。

@RunWith(Enclosed)
class InquiryInputControllerTest {

    ..........

    @RunWith(SpringRunner)
    @SpringBootTest
    static class 入力画面3のテスト {
        private InquiryInput01Form inquiryInput01Form_001 =
                (InquiryInput01Form) new Yaml().load(getClass().getResourceAsStream("InquiryInput01Form_001.yaml"))
        private InquiryInput02Form inquiryInput02Form_001 = new InquiryInput02Form(
                zipcode1: "102"
                , zipcode2: "0072"
                , address: "東京都千代田区飯田橋1-1"
                , tel1: "03"
                , tel2: "1234"
                , tel3: "5678"
                , email: "taro.tanaka@sample.co.jp")
        private InquiryInput03Form inquiryInput03Form_001 = new InquiryInput03Form(
                type1: Type1Values.PRODUCT.value
                , type2: [Type2Values.ESTIMATE.value, Type2Values.CATALOGUE.value, Type2Values.OTHER.value]
                , inquiry: "これはテストです"
                , survey: ["1", "2", "3", "4", "5", "6", "7", "8"])

        @Autowired
        private WebApplicationContext context

        MockMvc mockMvc

        @Before
        void setup() {
            mockMvc = MockMvcBuilders.webAppContextSetup(context)
                    .build()
        }

        @Test
        void "初期表示時は画面の項目には何もセットされない"() {
            expect:
            mockMvc.perform(get("/inquiry/input/03"))
                    .andExpect(status().isOk())
                    .andExpect(html("select[name='type1'] option[selected]").notExists())
                    .andExpect(html("input[name='type2'][checked='checked']").notExists())
                    .andExpect(html("#inquiry").val(""))
                    .andExpect(html("input[name='survey'][checked='checked']").notExists())
        }

        @Test
        void "項目全てに入力して前の画面へ戻るボタンをクリックすると入力画面2へ戻り、次へ戻るボタンを押して入力画面3へ戻ると以前入力したデータがセットされて表示される"() {
            when: "入力画面1で項目全てに入力して「次へ」ボタンをクリックする"
            MvcResult result = mockMvc.perform(
                    TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrlPattern("**/inquiry/input/02"))
                    .andReturn()
            MockHttpSession session = result.getRequest().getSession()

            and: "入力画面2で項目全てに入力して「次へ」ボタンをクリックする"
            mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001)
                    .session(session))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrlPattern("**/inquiry/input/03"))

            and: "入力画面3で項目全てに入力して「前の画面へ戻る」ボタンをクリックする"
            mockMvc.perform(TestHelper.postForm("/inquiry/input/03?move=back", inquiryInput03Form_001)
                    .session(session))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrlPattern("**/inquiry/input/02"))

            and: "入力画面2で「次へ」ボタンをクリックする"
            mockMvc.perform(TestHelper.postForm("/inquiry/input/02?move=next", inquiryInput02Form_001)
                    .session(session))
                    .andExpect(status().isFound())
                    .andExpect(redirectedUrlPattern("**/inquiry/input/03"))

            then: "入力画面3が以前入力したデータがセットされて表示される"
            mockMvc.perform(get("/inquiry/input/03").session(session))
                    .andExpect(status().isOk())
                    .andExpect(html("select[name='type1'] option[selected]").val(inquiryInput03Form_001.type1))
                    .andExpect(html("input[name='type2'][checked='checked']").count(3))
                    .andExpect(html("#inquiry").val(inquiryInput03Form_001.inquiry))
                    .andExpect(html("input[name='survey'][checked='checked']").count(8))
        }

        @Test
        void "項目全てに入力して次へボタンをクリックすると確認画面へ遷移し、前の画面へ戻るボタンを押して入力画面3へ戻ると以前入力したデータがセットされて表示される"() {
            expect:
            assert false, "確認画面を実装してからテストを作成する"
        }

        @Test
        void "入力チェックエラーのあるデータで「次へ」ボタンをクリックするとIllegalArgumentExceptionが発生する"() {
            setup: "入力チェックエラーになるデータを用意する"
            inquiryInput03Form_001.type1 = ""

            expect: "入力画面3の「次へ」ボタンをクリックする"
            Throwable thrown = catchThrowable({
                mockMvc.perform(
                        TestHelper.postForm("/inquiry/input/03?move=next", inquiryInput03Form_001))
                        .andExpect(status().isOk())
            })
            assertThat(thrown.cause).isInstanceOf(IllegalArgumentException)
        }

    }

}
  • 入力画面3のテスト クラスを追加します。
  • 項目全てに入力して次へボタンをクリックすると確認画面へ遷移し、前の画面へ戻るボタンを押して入力画面3へ戻ると以前入力したデータがセットされて表示される のテストは確認画面を実装しないとテストを作成できなかったので、今は常に失敗するようにします。

テストを実行して1つを除き成功することを確認します。

f:id:ksby:20180526193719p:plain

次回は。。。

確認画面を作成するか、以下3つのどれかをやりたいと思います。

履歴

2018/05/26
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その51 )( 入力画面3を作成する4 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その50 )( 入力画面3を作成する3 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面3の作成
    • 「前の画面へ戻る」ボタン、「確認画面へ」ボタンが正常に動作していないので原因を調査します。
    • その後にサーバ側の処理を実装します。

参照したサイト・書籍

目次

  1. 「前の画面へ戻る」ボタンを押すと固まる原因を調査する
  2. 「確認画面へ」ボタンを押すとエラーになる原因を調査する
  3. Form クラスを作成する
  4. input03.html, input03.js を変更する
  5. SessionData クラスを変更する
  6. 画面表示時と「前の画面へ戻る」「確認画面へ」ボタンクリック時の処理を実装する
  7. 動作確認

手順

「前の画面へ戻る」ボタンを押すと固まる原因を調査する

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その41 )( IntelliJ IDEA で Javascript を debug する ) に書いた手順で debug してみたら form タグの id 属性の文字列が input03.js に書いている id と一致していないことが原因でした。

src/main/assets/js/inquiry/input03.js を以下のように変更します。

          <form id="inquiryInput03Form" class="form-horizontal" method="post" action=""
                th:action="@{/inquiry/input/03/}">
  • input03ForminquiryInput03Form に変更します。

入力画面3を表示して「前の画面へ戻る」ボタンを押すと、今度は入力画面2に戻りました。

f:id:ksby:20180509223058p:plain f:id:ksby:20180509223402p:plain

「確認画面へ」ボタンを押すとエラーになる原因を調査する

上の調査で debug していた時に気づきましたが、原因は「確認画面へ」ボタンの class 属性に記述しているクラス名が js-btn-confirm なのに input03.js に書いているクラス名が js-btn-next だったからでした。

src/main/assets/js/inquiry/input03.js を以下のように変更します。

var btnBackOrNextClickHandler = function (event, url, ignoreCheckRequired) {
    ..........

    // 「前の画面へ戻る」「次へ」ボタンをクリック不可にする
    $(".js-btn-back").prop("disabled", true);
    $(".js-btn-confirm").prop("disabled", true);

    ..........
};

$(document).ready(function () {
    ..........

    // 「前の画面へ戻る」「次へ」ボタンクリック時の処理をセットする
    $(".js-btn-back").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=back", true);
    });
    $(".js-btn-confirm").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=next", false);
    });

    ..........
});
  • .js-btn-next.js-btn-confirm に変更します。

動作確認します。入力画面3を表示して、

f:id:ksby:20180509230213p:plain

何も選択・入力せずに「確認画面へ」ボタンを押すと、入力画面3のままで必須項目にエラーメッセージが表示されました。

f:id:ksby:20180509230338p:plain

必須項目全てに選択・入力してから「確認画面へ」ボタンを押すと、

f:id:ksby:20180509230451p:plain

今度は確認画面が表示されました。

f:id:ksby:20180509230707p:plain

Form クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form の下に InquiryInput03Form.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry.form;


import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 入力画面3用 Form クラス
 */
@Data
public class InquiryInput03Form implements Serializable {

    private static final long serialVersionUID = -2818250124844174764L;

    private String type1;

    private List<String> type2 = new ArrayList<>();

    private String inquiry;

    private List<String> survey;

    private boolean copiedFromSession = false;

}

必須チェックだけ実行するためのクラスも作ります。src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form の下に InquiryInput03FormNotEmptyRule.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry.form;


import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;

import java.util.ArrayList;
import java.util.List;

/**
 * 入力画面3 必須チェック用クラス
 */
@Data
public class InquiryInput03FormNotEmptyRule {

    @NotEmpty
    private String type1;

    @NotEmpty
    private List<String> type2 = new ArrayList<>();

    @NotEmpty
    private String inquiry;

    private List<String> survey;

    private boolean copiedFromSession = false;

}

input03.html, input03.js を変更する

src/main/resources/templates/web/inquiry/input03.html の以下の点を変更します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面3</title>

  <style>
    /* 「チェックボックス複数行」の入力項目のチェックボックスを複数行に書くので、 */
    /* 異なる行のチェックボックスの位置を左揃えにする                         */
    @media (min-width: 768px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 180px;
      }
    }
    @media (max-width: 767px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 100%;
      }
    }
  </style>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        入力画面3
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <!--/*@thymesVar id="inquiryInput03Form" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form"*/-->
          <form id="inquiryInput03Form" class="form-horizontal" method="post" action=""
                th:action="@{/inquiry/input/03/}"
                th:object="${inquiryInput03Form}">
            <input type="hidden" name="copiedFromSession" id="copiedFromSession" th:value="*{copiedFromSession}"/>

            <!-- お問い合わせの種類1 -->
            <div class="form-group" id="form-group-type1">
              <div class="control-label col-sm-2">
                <label class="float-label">お問い合わせの種類1</label>
                <div class="label label-required">必須</div>
              </div>
              <div class="col-sm-10">
                <div class="row"><div class="col-sm-10">
                  <select name="type1" id="type1" class="form-control" style="width: 250px;" autofocus>
                    <th:block th:each="type1Value,iterStat : ${@vh.values('Type1Values')}">
                      <option value="" th:if="${iterStat.first}">選択してください</option>
                      <option th:value="${type1Value.value}" th:text="${type1Value.text}"
                        th:field="*{type1}">
                      </option>
                    </th:block>
                  </select>
                </div></div>
                <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div>
              </div>
            </div>

            <!-- お問い合わせの種類2 -->
            <div class="form-group" id="form-group-type2">
              <div class="control-label col-sm-2">
                <label class="float-label">お問い合わせの種類2</label>
                <div class="label label-required">必須</div>
              </div>
              <div class="col-sm-10">
                <div class="row"><div class="col-sm-10">
                  <div class="checkbox">
                    <th:block th:each="type2Value : ${@vh.values('Type2Values')}">
                      <label>
                        <input type="checkbox" name="type2" th:value="${type2Value.value}"
                          th:field="*{type2}">
                        <th:block th:text="${type2Value.text}">見積が欲しい</th:block>
                      </label>
                    </th:block>
                  </div>
                </div></div>
                <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div>
              </div>
            </div>

            <!-- お問い合わせの内容 -->
            <div class="form-group" id="form-group-inquiry">
              <div class="control-label col-sm-2">
                <label class="float-label">お問い合わせの内容</label>
                <div class="label label-required">必須</div>
              </div>
              <div class="col-sm-10">
                <div class="row"><div class="col-sm-10">
                  <textarea rows="5" name="inquiry" id="inquiry" class="form-control" maxlength="500" placeholder="お問い合わせ内容を入力して下さい"
                    th:field="*{inquiry}"></textarea>
                </div></div>
                <div class="row"><div class="col-sm-10"><p class="form-control-static"><small>※最大500文字</small></p></div></div>
                <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div>
              </div>
            </div>

            <div class="form-group" id="form-group-survey">
              <div class="control-label col-sm-2">
                <label class="float-label">アンケート</label>
              </div>
              <div class="col-sm-10" id="multiline-checkbox">
                <th:block th:each="surveyOptions,iterStat : ${@soh.selectItemList('survey')}">
                  <th:block th:if="${iterStat.index % 3 == 0}"
                            th:utext="'&lt;div class=&quot;row&quot;&gt;&lt;div class=&quot;col-sm-12&quot;&gt;&lt;div class=&quot;checkbox&quot;&gt;'"/>

                  <label>
                    <input type="checkbox" name="survey" th:value="${surveyOptions.itemValue}"
                      th:field="*{survey}">
                    <th:block th:text="${surveyOptions.itemName}">選択肢1だけ長くしてみる</th:block>
                  </label>

                  <th:block th:if="${iterStat.index % 3 == 2 || iterStat.last}"
                            th:utext="'&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;'"/>
                </th:block>
              </div>
            </div>

            <div class="text-center">
              <button class="btn bg-blue js-btn-back"><i class="fa fa-arrow-left"></i> 前の画面へ戻る</button>
              <button class="btn bg-green js-btn-confirm"><i class="fa fa-arrow-right"></i> 確認画面へ</button>
            </div>
          </form>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->
<script src="/js/inquiry/input03.js"></script>

</body>
</html>
  • form タグの上に <!--/*@thymesVar id="inquiryInput03Form" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form"*/--> を追加します。
  • form タグの末尾に th:object="${inquiryInput03Form}" を追加します。
  • <input type="hidden" name="copiedFromSession" id="copiedFromSession" th:value="*{copiedFromSession}"/> を追加します。
  • <select name="type1" id="type1" class="form-control" style="width: 250px;" autofocus><select name="type1" id="type1" class="form-control" style="width: 250px;"> に変更します。
  • 入力/選択項目のタグに th:field="*{...}"(... には入力項目に対応した変数を記述) を追加します。

src/main/assets/js/inquiry/input03.js の以下の点を変更します。

$(document).ready(function (event) {
    // 入力チェック用の validator 関数をセットする
    $("#type1").on("blur", type1Validator);
    $("input:checkbox[name='type2']").on("blur", type2Validator);
    $("#inquiry").on("blur", inquiryValidator);

    // 「前の画面へ戻る」「次へ」ボタンクリック時の処理をセットする
    $(".js-btn-back").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=back", true);
    });
    $(".js-btn-confirm").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=next", false);
    });

    // 初期画面表示時にセッションに保存されていたデータを表示する場合には
    // 入力チェックを実行して画面の表示を入力チェック後の状態にする
    if ($("#copiedFromSession").val() === "true") {
        executeAllValidator(event);
    }

    // 「お問い合わせの種類1」にフォーカスをセットする
    $("#type1").focus().select();
});
  • $(document).ready(function () {$(document).ready(function (event) { に変更します。
  • if ($("#copiedFromSession").val() === "true") { ... } を追加します。

SessionData クラスを変更する

src/main/java/ksbysample/webapp/bootnpmgeb/session/SessionData.java の以下の点を変更します。

@Data
public class SessionData implements Serializable {

    private static final long serialVersionUID = -2673191456750655164L;

    private InquiryInput01Form inquiryInput01Form;

    private InquiryInput02Form inquiryInput02Form;

    private InquiryInput03Form inquiryInput03Form;

}
  • private InquiryInput03Form inquiryInput03Form; を追加します。

画面表示時と「前の画面へ戻る」「確認画面へ」ボタンクリック時の処理を実装する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の以下の点を変更します。

@Slf4j
@Controller
@RequestMapping("/inquiry/input")
@SessionAttributes("sessionData")
public class InquiryInputController {

    ..........

    /**
     * 入力画面3 初期表示処理
     *
     * @return 入力画面3の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/03")
    public String input03(InquiryInput03Form inquiryInput03Form
            , SessionData sessionData) {
        // セッションに保存されているデータがある場合にはコピーする
        if (sessionData.getInquiryInput03Form() != null) {
            modelMapper.map(sessionData.getInquiryInput03Form(), inquiryInput03Form);
            inquiryInput03Form.setCopiedFromSession(true);
        }

        return TEMPLATE_INPUT03;
    }

    /**
     * 入力画面3 「前へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/03", params = {"move=back"})
    public String input03MoveBack(@Validated InquiryInput03Form inquiryInput03Form
            , BindingResult bindingResult
            , SessionData sessionData
            , UriComponentsBuilder builder) {
        if (bindingResult.hasErrors()) {
            bindingResult.getAllErrors().stream().forEach(e -> log.warn(e.getCode()));
            throw new IllegalArgumentException("セットされるはずのデータがセットされていません");
        }

        // 入力されたデータをセッションに保存する
        sessionData.setInquiryInput03Form(inquiryInput03Form);

        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString();
    }

    /**
     * 入力画面3 「確認画面へ」ボタンクリック時の処理
     *
     * @return 確認画面の URL
     */
    @PostMapping(value = "/03", params = {"move=next"})
    public String input03MoveNext(@Validated InquiryInput03Form inquiryInput03Form
            , BindingResult bindingResult
            , InquiryInput03FormNotEmptyRule inquiryInput03FormNotEmptyRule
            , SessionData sessionData
            , UriComponentsBuilder builder) {
        // 必須チェックをする
        mvcValidator.validate(inquiryInput03FormNotEmptyRule, bindingResult);
        if (bindingResult.hasErrors()) {
            bindingResult.getAllErrors().stream().forEach(e -> log.warn(e.getCode()));
            throw new IllegalArgumentException("セットされるはずのデータがセットされていません");
        }

        // 入力されたデータをセッションに保存する
        sessionData.setInquiryInput03Form(inquiryInput03Form);

        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_CONFIRM).toUriString();
    }

}
  • input03 メソッドの以下の点を変更します。
    • 引数に InquiryInput03Form inquiryInput03FormSessionData sessionData を追加します。
    • if (sessionData.getInquiryInput02Form() != null) { ... } の処理を追加します。
  • input03MoveBack メソッドの以下の点を変更します。
    • 引数に @Validated InquiryInput03Form inquiryInput03FormBindingResult bindingResultSessionData sessionData を追加します。
    • if (bindingResult.hasErrors()) { ... } の処理を追加します。
    • sessionData.setInquiryInput03Form(inquiryInput03Form); を追加します。
  • input03MoveNext メソッドの以下の点を変更します。
    • 引数に @Validated InquiryInput03Form inquiryInput03FormBindingResult bindingResultInquiryInput03FormNotEmptyRule inquiryInput03FormNotEmptyRuleSessionData sessionData を追加します。
    • mvcValidator.validate(inquiryInput03FormNotEmptyRule, bindingResult); を追加します。
    • if (bindingResult.hasErrors()) { ... } を追加します。
    • sessionData.setInquiryInput03Form(inquiryInput03Form); を追加します。

動作確認

動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/03/ にアクセスします。

データを入力してから、「次へ」ボタンをクリックして入力画面3へ遷移します。

f:id:ksby:20180519180336p:plain

何も入力せずに「前の画面へ戻る」ボタンをクリックすると、入力画面2へ戻ります。サーバ側でも必須チェックは行われません。

f:id:ksby:20180519180651p:plain

「次へ」ボタンをクリックして入力画面3へ戻った後、データを入力します。

f:id:ksby:20180519180943p:plain

「前の画面へ戻る」ボタンをクリックして入力画面2へ戻ってから、

f:id:ksby:20180519181410p:plain

「次へ」ボタンをクリックして入力画面3へ戻ると、前に入力したデータが表示されます。

f:id:ksby:20180519181723p:plain

入力したデータを変更してから、

f:id:ksby:20180519182114p:plain

「確認画面へ」ボタンをクリックして確認画面へ遷移した後、

f:id:ksby:20180519182247p:plain

一番下の「修正する」ボタンをクリックすると、入力画面3へ戻り変更したデータが表示されます。

f:id:ksby:20180519182600p:plain

問題なく動作しているようです。

履歴

2018/05/19
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その50 )( 入力画面3を作成する3 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その49 )( 入力画面3を作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面3の作成
    • Javascript の処理を実装します。

参照したサイト・書籍

目次

  1. 入力チェックを実装する
  2. input03.html を修正する
  3. 動作確認

手順

入力チェックを実装する

src/main/assets/js/inquiry/input03.js を以下のように変更します。

"use strict";

var Form = require("lib/class/Form.js");
var validator = require("lib/util/validator.js");

var form = new Form([
    "#type1",
    "input:checkbox[name='type2']",
    "#inquiry",
    "input:checkbox[name='survey']"
]);

var type1Validator = function (event) {
    var idFormGroup = "#form-group-type1";
    var idList = ["#type1"];
    form.convertAndValidate(form, event, idFormGroup, idList,
        undefined,
        function () {
            validator.checkRequired(form, idFormGroup, idList, "お問い合わせの種類1を選択してください");
        }
    );
};

var type2Validator = function (event) {
    var idFormGroup = "#form-group-type2";
    var idList = ["input:checkbox[name='type2']"];
    form.convertAndValidate(form, event, idFormGroup, idList,
        undefined,
        function () {
            validator.checkRequired(form, idFormGroup, idList, "お問い合わせの種類2を選択してください");
        }
    );
};

var inquiryValidator = function (event) {
    var idFormGroup = "#form-group-inquiry";
    var idList = ["#inquiry"];
    form.convertAndValidate(form, event, idFormGroup, idList,
        undefined,
        function () {
            validator.checkRequired(form, idFormGroup, idList, "お問い合わせの内容を入力してください");
        }
    );
};

var executeAllValidator = function (event) {
    form.forceAllFocused(form);
    [
        type1Validator,
        type2Validator,
        inquiryValidator
    ].forEach(function (validateFunction) {
        validateFunction(event);
    });
};

var btnBackOrNextClickHandler = function (event, url, ignoreCheckRequired) {
    // 全ての入力チェックを実行する
    try {
        if (ignoreCheckRequired) {
            validator.ignoreCheckRequired = ignoreCheckRequired;
            form.backupFocusedState(form);
        }
        executeAllValidator(event);
    } finally {
        if (ignoreCheckRequired) {
            validator.reset();
            form.restoreFocusedState(form);
        }
    }
    // 入力チェックエラーがある場合には処理を中断する
    if (event.isPropagationStopped()) {
        // 一番最初のエラーの項目にカーソルを移動する
        $(".has-error:first :input:first").focus().select();
        return false;
    }

    // 「前の画面へ戻る」「次へ」ボタンをクリック不可にする
    $(".js-btn-back").prop("disabled", true);
    $(".js-btn-next").prop("disabled", true);

    // サーバにリクエストを送信する
    $("#ignoreCheckRequired").val(ignoreCheckRequired);
    $("#inquiryInput03Form").attr("action", url);
    $("#inquiryInput03Form").submit();

    // return false は
    // event.preventDefault() + event.stopPropagation() らしい
    return false;
};

$(document).ready(function () {
    // 入力チェック用の validator 関数をセットする
    $("#type1").on("blur", type1Validator);
    $("input:checkbox[name='type2']").on("blur", type2Validator);
    $("#inquiry").on("blur", inquiryValidator);

    // 「前の画面へ戻る」「次へ」ボタンクリック時の処理をセットする
    $(".js-btn-back").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=back", true);
    });
    $(".js-btn-next").on("click", function (e) {
        return btnBackOrNextClickHandler(e, "/inquiry/input/03/?move=next", false);
    });

    // 「お問い合わせの種類1」にフォーカスをセットする
    $("#type1").focus().select();
});

input03.html を修正する

「次へ」ボタンはいつでも押せるようにします。src/main/resources/templates/web/inquiry/input02.html を以下のように変更します。

            <div class="text-center">
              <button class="btn bg-blue js-btn-back"><i class="fa fa-arrow-left"></i> 前の画面へ戻る</button>
              <button class="btn bg-green js-btn-confirm"><i class="fa fa-arrow-right"></i> 確認画面へ</button>
            </div>
  • <button class="btn bg-green js-btn-confirm" disabled><button class="btn bg-green js-btn-confirm"> へ変更します。

動作確認

npm run springboot を実行し、Tomcat を起動して http://localhost:9080/inquiry/input/03/ にアクセスすると入力画面3が表示されます。

f:id:ksby:20180509005649p:plain

何も入力せずに Tab キーでカーソルを「前の画面へ戻る」ボタンまで移動すると赤色になってエラーメッセージが表示されます。

f:id:ksby:20180509005833p:plain

F5 キーを押してリロードした後、全ての項目にエラーにならないようデータを選択・入力すると、「アンケート」以外の項目が緑色になります。

f:id:ksby:20180509010805p:plain

Javascript の入力チェックは正常に動作しているようです。

ただし「前の画面へ戻る」ボタンを押すと入力画面3のまま固まって入力画面2へは戻らず、「確認画面へ」ボタンを押すとエラーページが表示されました(入力データは保存しないが画面遷移だけはしたはずだった気が。。。) 次回は画面遷移しない原因を調べてみます。

f:id:ksby:20180509011330p:plain

履歴

2018/05/09
初版発行。

Java SE を 8u162 → 8u172 へ、IntelliJ IDEA を 2017.3.4 → 2017.3.5 → 2018.1.2 へ、Git for Windows を 2.16.2 → 2.17.0 へバージョンアップ

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

Java SE を 8u162 → 8u172 へバージョンアップする

  1. OracleJava SE Downloads を見ると 8u172 がダウンロードできるようになっていましたので、8u172 へバージョンアップします。

  2. jdk-8u172-windows-x64.exe をダウンロードして C:\Java\jdk1.8.0_172 へインストールした後、環境変数 JAVA_HOME のパスを C:\Java\jdk1.8.0_172 へ変更します。

    コマンドプロンプトから java -version を実行し、1.8.0_172 に変更されていることを確認します。

    f:id:ksby:20180425132500p:plain

  3. IntelliJ IDEA を再起動した後、プロジェクトで使用する Java SE を 8u172 へ変更します。

  4. 開いているプロジェクトを閉じて「Welcome to IntelliJ IDEA」ダイアログを表示します。

  5. ダイアログ下部の「Configure」-「Project Defaults」-「Project Structure」を選択します。

    f:id:ksby:20180425140735p:plain

  6. 「Default Project Structure」ダイアログが表示されます。画面左側で「Project Settings」-「Project」を選択後、画面右側の「Project SDK」の「New...」ボタンをクリックし、表示されるメニューから「JDK」を選択します。

    f:id:ksby:20180425141038p:plain

  7. 「Select Home Directory for JDK」ダイアログが表示されます。C:\Java\jdk1.8.0_172 を選択した後、「OK」ボタンをクリックします。

    f:id:ksby:20180425141407p:plain

  8. 「Default Project Structure」ダイアログに戻るので、今度は「Project SDK」の「Edit」ボタンをクリックします。

    f:id:ksby:20180425141552p:plain

  9. 画面左側で「Platform Settings」-「SDKs」が選択された状態になるので、画面右上の入力フィールドで "1.8" → "1.8.0_172" へ変更します。

    f:id:ksby:20180425141841p:plain

  10. 次に中央のリストから「1.8.0_162」を選択した後、リストの上の「-」ボタンをクリックして削除します。

    f:id:ksby:20180425142547p:plain

  11. 「OK」ボタンをクリックして「Default Project Structure」ダイアログを閉じます。

  12. 「Welcome to IntelliJ IDEA」ダイアログに戻ったら、ksbysample-webapp-lending プロジェクトを開きます。

  13. IntelliJ IDEA のメイン画面が開いたら、メニューから「File」-「Project Structure...」を選択します。

  14. 「Project Structure」ダイアログが表示されます。以下の画像の状態になっているので、

    f:id:ksby:20180425142824p:plain

    「Project SDK」と「Project language level」を選択し直します。

    f:id:ksby:20180425143217p:plain

  15. 「OK」ボタンをクリックして「Project Structure」ダイアログを閉じます。

  16. メイン画面に戻ると画面右下に「Indexing...」の表示が出るので、終了するまで待ちます。

    f:id:ksby:20180425143359p:plain

  17. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20180425144115p:plain

  18. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20180425144608p:plain

  19. 特に問題は発生しませんでした。8u172 で開発を進めます。

IntelliJ IDEA を 2017.3.4 → 2017.3.5 へバージョンアップする

IntelliJ IDEA の最新版は 2018.1.2 ですが、メインメニューの「Check for Updates...」を選択すると 2017.3.5 へのバージョンアップが表示されるので、一旦 2017.3.5 へバージョンアップしてから 2018.1.2 へバージョンアップすることにします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180425202248p:plain

  3. Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180425202349p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20180425202924p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.3.5 へバージョンアップされていることを確認します。

  7. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。(キャプチャは省略します)

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。(キャプチャは省略します)

  9. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。(キャプチャは省略します)

IntelliJ IDEA を 2017.3.5 → 2018.1.2 へバージョンアップする

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。今回は左下に「Update and Restart」ボタンが表示されていないので、

    f:id:ksby:20180425204910p:plain

    「Download」ボタンを押して IntelliJ IDEA の Download ページを開いた後、「DOWNLOAD」ボタンを押して ideaIU-2018.1.2.exe をダウンロードします。

    f:id:ksby:20180425205119p:plain

  3. 起動している IntelliJ IDEA を終了します。

  4. ideaIU-2018.1.2.exe を実行します。

  5. IntelliJ IDEA Setup」ダイアログが表示されます。「Next >」ボタンをクリックします。

    f:id:ksby:20180425211159p:plain

  6. 「Uninstall old versions」画面が表示されます。画面上の全てのチェックボックスをチェックした後、「Next >」ボタンをクリックします。

    f:id:ksby:20180425211312p:plain

  7. 「Choose Install Location」画面が表示されます。「Destination Folder」を C:\IntelliJ_IDEA\2018.1.2 に変更した後、「Next >」ボタンをクリックします。

    f:id:ksby:20180425211752p:plain

  8. 「Installation Options」画面が表示されます。何も変更せずに「Next >」ボタンをクリックします。

    f:id:ksby:20180425211852p:plain

  9. 「Choose Start Menu Folder」画面が表示されます。何も変更せずに「Install」ボタンをクリックします。

    f:id:ksby:20180425211943p:plain

  10. 「Installing」画面が表示されてインストールが始まりますので、完了するまで待ちます。

  11. インストールが完了すると「Completing IntelliJ IDEA Setup」画面が表示されます。「Finish」ボタンをクリックしてダイアログを閉じます。

    f:id:ksby:20180425212228p:plain

  12. C:\IntelliJ_IDEA\2017.3.1 ディレクトリが残っているので削除します。

  13. C:\IntelliJ_IDEA\2018.1.2\bin\idea64.exe を実行します。

  14. 「Complete Installation」ダイアログが表示されます。何も変更せずに「OK」ボタンをクリックします。

    f:id:ksby:20180425212457p:plain

  15. IntelliJ IDEA のメイン画面が表示され画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20180425212715p:plain

  16. Plugin が全て最新にアップデートされていないようなので先にアップデートします。IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  17. IDE and Plugin Updates」ダイアログが表示されます。何も変更せずに「Update」ボタンをクリックします。

    f:id:ksby:20180425213036p:plain

    Patch がダウンロードされた後、IntelliJ IDEA を再起動します。再起動後、画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

  18. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.1.2 へバージョンアップされていることを確認します。

  19. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20180425213704p:plain

  20. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20180425214453p:plain

  21. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。。。が、かなりのテストが失敗しました。

    f:id:ksby:20180426003323p:plain

    java.lang.RuntimeException: java.nio.file.NoSuchFileException: src\test\resources\testdata\base\table-ordering.txt というエラーが出力されています。gradle からのテストは成功しているので、IntelliJ IDEA からテストを実行する時のカレントディレクトリが変わったような気がします。

    IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示し、JUnit の設定を見ると「Working directory」という項目があり $MODULE_DIR$ という値が設定されています。

    f:id:ksby:20180428233415p:plain

    右側の「...」ボタンを押すと C:\project-springboot\ksbysample-webapp-lending.idea\modules が表示されました。プロジェクトのルートディレクトリではありませんね。。。

    f:id:ksby:20180428233611p:plain

    「Working directory」の設定をクリアしてテストが成功するようになるか試してみます。

    f:id:ksby:20180428234024p:plain

    IntelliJ IDEA の画面右上で「All in ksbysample-webapp-lending_test」の Configuration を選択した後 Run ボタンを押すと、

    f:id:ksby:20180428234312p:plain

    今度は全てのテストが成功しました。「Working directory」の設定をクリアしても問題なさそうです。

    f:id:ksby:20180428234728p:plain

    再び IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示します。左の一覧から「Defaults」-「JUnit」を選択し、画面右側で「Working directory」を空にして「OK」ボタンをクリックします。

    f:id:ksby:20180429102348p:plain

    Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択すると、今度はテストが全て成功しました。

    f:id:ksby:20180429103003p:plain

  22. 前回のバージョンアップ時に削除しなかった C:\Users\root.IntelliJIdea2017.2 と、C:\Users\root.IntelliJIdea2017.3 を削除します。

Git for Windows を 2.16.2 → 2.17.0 へバージョンアップする

Git for Windows の 2.17.0 がリリースされていたのでバージョンアップします。

  1. https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.17.0-64-bit.exe をダウンロードします。

  2. 「Git 2.17.0 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。

  3. 「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。

  4. 「Choosing the default editor used by Git」画面が表示されます。「Use Vim (the ubiquitous text editor) as Git's default editor」が選択された状態で [Next >]ボタンをクリックします。

  5. 「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。

  6. 「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。

  7. 「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  8. 「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  9. 「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。

  10. インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。

  11. コマンドプロンプトを起動して git --version を実行し、git のバージョンが git version 2.17.0.windows.1 になっていることを確認します。

    f:id:ksby:20180502054602p:plain

  12. git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。

    f:id:ksby:20180502054752p:plain

  13. 特に問題はないようですので、2.17.0 で作業を進めたいと思います。